Rebuild graph update system with contract-driven relationship semantics v2 rebuild: EF Core 10 targeting with modular refactoring#13
Conversation
Update actions/checkout from v2 to v4, actions/setup-dotnet from v1 to v4, and change dotnet-version to "10.0.x". Also fix indentation and remove extra spaces in branch names.
Add a new publish job to the CI pipeline that builds, packs, and publishes the NuGet package on master pushes, with conditional checks for API key availability. Update action versions (checkout@v6, setup-dotnet@v5), adjust commands to target the solution file, and refine job names and permissions. BREAKING CHANGE: package version bumped from 2.0.0 to 10.0.0 to align with EF Core 10+ targeting, which may introduce incompatibilities.
…cs; targeting EF Core 10
📝 WalkthroughWalkthroughRefactors graph-update into a GraphUpdateOrchestrator with strategy modules, adds typed exceptions, migrates the library and tests to .NET 10 / EF Core 10, replaces legacy fake model/tests with a new TestModel and containerized integration test suite, and introduces many new unit and integration tests. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Caller as Caller
participant Orch as GraphUpdateOrchestrator
participant Guard as OperationGuard
participant Loader as NavigationLoadGuard
participant Strategy as RelationshipStrategy
participant Db as DbContext
Caller->>Orch: UpdateGraph(context, updated, existing)
activate Orch
Orch->>Loader: EnsureNavigationsLoaded(existing)
Loader-->>Orch: loaded/unloaded info
Orch->>Guard: ValidateNavigations(updated, existing)
Guard-->>Orch: validation result
alt validation failed
Orch-->>Caller: throw GraphUpdateException / PartialMutationNotAllowedException
else validation passed
Orch->>Db: Update scalar properties on existing
Orch->>Strategy: For each loaded navigation: Classify -> Apply changes
Strategy->>Db: add/remove/update tracked entities & links
Strategy-->>Orch: applied
Orch-->>Caller: return tracked existing entity
end
deactivate Orch
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (16)
src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Diwink.Extensions.EntityFrameworkCore.Tests.Integration.csproj (1)
12-15: Pin floating package versions to keep CI restores deterministic.Lines 12, 13, and 15 use wildcard versions (
8.*,10.*,4.*), which can resolve differently over time and make integration test behavior drift between runs.Proposed change
- <PackageReference Include="FluentAssertions" Version="8.*" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.*" /> + <PackageReference Include="FluentAssertions" Version="8.9.0" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.5" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> - <PackageReference Include="Testcontainers.MsSql" Version="4.*" /> + <PackageReference Include="Testcontainers.MsSql" Version="4.0.0" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Diwink.Extensions.EntityFrameworkCore.Tests.Integration.csproj` around lines 12 - 15, Replace the floating wildcard package versions with exact, pinned versions to ensure deterministic CI restores: update the PackageReference entries for FluentAssertions, Microsoft.EntityFrameworkCore.SqlServer, and Testcontainers.MsSql (the elements with Include="FluentAssertions", Include="Microsoft.EntityFrameworkCore.SqlServer", Include="Testcontainers.MsSql") to use explicit version numbers instead of "8.*", "10.*", and "4.*" respectively (leave Microsoft.NET.Test.Sdk as-is if already pinned); commit the csproj change so CI always restores the specified exact package versions.src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cs (1)
12-23: ValidaterelationshipTypebefore storing it.Line 22 sets a contract property from unvalidated input; enforce non-empty values to keep error payloads dependable.
Proposed fix
public UnsupportedNavigationMutatedException( string relationshipPath, string relationshipType) : base( $"Mutation detected in unsupported navigation '{relationshipPath}' " + $"(relationship type: {relationshipType}). This relationship type is not " + "in the v2 supported contract. The entire operation was rejected. " + "Unchanged unsupported navigations would have been silently skipped.", relationshipPath) { + ArgumentException.ThrowIfNullOrWhiteSpace(relationshipType); RelationshipType = relationshipType; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cs` around lines 12 - 23, The UnsupportedNavigationMutatedException constructor stores the unvalidated parameter relationshipType into the RelationshipType property; validate that relationshipType is not null/empty (e.g., throw ArgumentNullException or ArgumentException after trimming) before assigning it. Update the UnsupportedNavigationMutatedException(string relationshipPath, string relationshipType) constructor to check relationshipType (and relationshipPath if desired) and throw an appropriate exception for invalid values, then assign RelationshipType only after validation so error payloads remain dependable.src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs (1)
16-16: Return an immutable view forErrors.Line 16 exposes the backing
List<GraphUpdateException>instance. Internal callers can cast back toList<T>and mutate state outsideAddError.Proposed fix
- public IReadOnlyList<GraphUpdateException> Errors => _errors; + public IReadOnlyList<GraphUpdateException> Errors => _errors.AsReadOnly();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs` at line 16, The Errors property currently returns the mutable backing _errors list directly; change the getter to return an immutable view (e.g. wrap _errors with ReadOnlyCollection via _errors.AsReadOnly() or return a ReadOnlyCollection cached field) so callers cannot cast back to List<T> and mutate state; keep mutation confined to AddError and reference GraphUpdateException and _errors/Errors/AddError to locate the change.src/Diwink.Extensions.EntityFrameworkCore/Exceptions/AmbiguousOwnershipSemanticsException.cs (1)
11-20: ValidatemissingDetailto preserve exception quality.
MissingDetailis a public diagnostic field; Lines 11–20 should reject null/whitespace input.Proposed fix
public AmbiguousOwnershipSemanticsException( string relationshipPath, string missingDetail) : base( $"Ambiguous ownership semantics at '{relationshipPath}': {missingDetail}. " + "The contract requires explicit requiredness/ownership metadata.", relationshipPath) { + ArgumentException.ThrowIfNullOrWhiteSpace(missingDetail); MissingDetail = missingDetail; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/Exceptions/AmbiguousOwnershipSemanticsException.cs` around lines 11 - 20, The constructor AmbiguousOwnershipSemanticsException should validate the missingDetail parameter and reject null/whitespace to preserve exception integrity; inside the AmbiguousOwnershipSemanticsException(string relationshipPath, string missingDetail) constructor, check string.IsNullOrWhiteSpace(missingDetail) and throw an ArgumentException or ArgumentNullException with a clear message (including the parameter name "missingDetail") before calling base, ensuring the public MissingDetail property is only assigned valid non-empty text.src/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cs (1)
11-20: Enforce non-emptyunsupportedBranchinput.Line 19 persists
unsupportedBranchdirectly; null/blank values degrade the aggregated rejection message.Proposed fix
public PartialMutationNotAllowedException( string relationshipPath, string unsupportedBranch) : base( $"Graph operation rejected: unsupported mutation detected at '{unsupportedBranch}'. " + "The entire operation was rejected — partial mutation is not allowed.", relationshipPath) { + ArgumentException.ThrowIfNullOrWhiteSpace(unsupportedBranch); UnsupportedBranch = unsupportedBranch; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cs` around lines 11 - 20, The constructor PartialMutationNotAllowedException should validate that the incoming unsupportedBranch is not null/empty/whitespace before assigning; update the PartialMutationNotAllowedException(string relationshipPath, string unsupportedBranch) constructor to check string.IsNullOrWhiteSpace(unsupportedBranch) and throw an ArgumentException or ArgumentNullException with a clear parameter name if invalid, then assign UnsupportedBranch only after validation so the aggregated rejection message never contains an empty value.src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cs (1)
10-19: GuardpatternIdentifierbefore storing it as contract data.Line 18 stores
patternIdentifierwithout validation; empty values make rejection diagnostics ambiguous.Proposed fix
public UnsupportedRelationshipPatternException( string relationshipPath, string patternIdentifier) : base( $"Unsupported relationship pattern '{patternIdentifier}' at '{relationshipPath}'. " + "See the v2 contract documentation for supported patterns.", relationshipPath) { + ArgumentException.ThrowIfNullOrWhiteSpace(patternIdentifier); PatternIdentifier = patternIdentifier; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cs` around lines 10 - 19, The constructor for UnsupportedRelationshipPatternException stores patternIdentifier directly into the exception contract data without validation; add a guard in the UnsupportedRelationshipPatternException constructor to validate patternIdentifier (throw ArgumentNullException or ArgumentException for null/empty/whitespace) before assigning PatternIdentifier and before any contract-storage logic so consumers get a clear rejection diagnostic; update the constructor that currently sets PatternIdentifier to perform this check and throw a descriptive argument exception mentioning "patternIdentifier" when invalid.src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cs (1)
20-23: Add null guard before metadata dereference.A defensive check avoids an ambiguous NRE at Line 22 if a null foreign key is ever passed.
♻️ Suggested hardening
public static bool IsRequiredDependent(IForeignKey foreignKey) { + ArgumentNullException.ThrowIfNull(foreignKey); return foreignKey.IsRequired; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cs` around lines 20 - 23, IsRequiredDependent currently dereferences foreignKey.IsRequired without validation; add a null guard at the start of the method (in the IsRequiredDependent method) to validate the foreignKey parameter and throw an ArgumentNullException (include the parameter name) if it's null so the method fails fast with an explicit error instead of producing an ambiguous NRE.src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cs (1)
17-23: Add defensive argument guards at the strategy boundary.If upstream orchestration ever passes null, failure at Line 19 will be a less-informative NRE. Guarding inputs here improves diagnostics.
♻️ Suggested hardening
public static void RemoveDependent(DbContext context, ReferenceEntry existingNavigation) { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(existingNavigation); + var existingValue = existingNavigation.CurrentValue; if (existingValue is null) return; context.Remove(existingValue); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cs` around lines 17 - 23, RequiredOneToOneStrategy.RemoveDependent currently dereferences parameters without validation; add defensive argument guards at the strategy boundary by validating both context and existingNavigation are not null and throwing ArgumentNullException with appropriate param names (use nameof(context) and nameof(existingNavigation)) at the top of the RemoveDependent method before accessing existingNavigation.CurrentValue; keep the existing null-check for existingNavigation.CurrentValue as-is.src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/ManyToManySafetyContractTests.cs (1)
13-14: Redundant[Collection]attribute.The
[Collection(IntegrationTestCollection.Name)]attribute is already applied toIntegrationTestBase(Line 10 in that file). This attribute on the derived class is redundant.Proposed fix
-[Collection(IntegrationTestCollection.Name)] public class ManyToManySafetyContractTests : IntegrationTestBase🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/ManyToManySafetyContractTests.cs` around lines 13 - 14, The [Collection(IntegrationTestCollection.Name)] attribute on the ManyToManySafetyContractTests class is redundant because IntegrationTestBase already declares it; remove the attribute from the ManyToManySafetyContractTests class declaration so the class simply reads "public class ManyToManySafetyContractTests : IntegrationTestBase", and run tests to confirm no behavior change.src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cs (1)
48-75: Consider edge case:context.Findmay return stale data if not already tracked.When
context.Findreturns a database-loaded entity (Line 55), and you immediately callSetValues(Line 60), this works correctly. However, if the entity was already tracked with modifications from a previous operation within the same context lifetime, those modifications would be overwritten.This is likely acceptable given the expected usage pattern (fresh context per operation), but worth noting for future maintenance.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cs` around lines 48 - 75, context.Find can return an already-tracked entity whose in-memory changes would be clobbered by the unconditional context.Entry(knownEntity).CurrentValues.SetValues(updatedItem); modify the logic inside the PureManyToManyStrategy flow to first inspect the tracked state (via context.Entry(knownEntity).State) and only call SetValues when the entity was not previously modified (e.g., State is Unchanged or Detached); otherwise either merge selective properties or skip SetValues and just AddToCollection(knownEntity) so you don’t overwrite local changes from the same DbContext.src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/RequiredOneToOneContractTests.cs (1)
33-34: Unused variablepolicyVersion.The
policyVersionvariable is captured but never used in the test. Either use it in an assertion or remove it.Proposed fix
existing.Policy.Should().NotBeNull("seed data includes a CoursePolicy"); - var policyVersion = existing.Policy!.PolicyVersion;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/RequiredOneToOneContractTests.cs` around lines 33 - 34, The local variable policyVersion (assigned from existing.Policy!.PolicyVersion) is unused; either remove that assignment or assert something with it. Locate RequiredOneToOneContractTests and the lines referencing existing.Policy and policyVersion, then either delete the policyVersion assignment, or replace it with an assertion using policyVersion (for example assert the expected PolicyVersion from seed data against existing.Policy!.PolicyVersion) so the variable is consumed.src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cs (1)
84-125: Single-context pattern may cause change tracker interference.Unlike
Detects_added_skip_navigation_entries, this test uses a single context for seed, act, and assert phases. While this works with InMemory provider, it doesn't test the same code paths as production (where detached graphs are compared against freshly-loaded tracked entities).Also, Line 94:
CatalogId = Guid.NewGuid()references a non-existent catalog. InMemory provider doesn't enforce FK constraints, but this could mask real issues.For consistency, consider restructuring to match the multi-context pattern used in the first test.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cs` around lines 84 - 125, The test Detects_removed_skip_navigation_entries uses a single DbContext (CreateInMemoryContext) for arrange/act/assert which can let the change tracker hide bugs; refactor it to follow the multi-context pattern used in Detects_added_skip_navigation_entries by seeding data in one context (ctx seed), then opening a fresh context to load existing via Include(c => c.Tags) into a tracked entity (existing), build an updated detached Course object (updated) with the same CatalogId as the seeded course (do not call CatalogId = Guid.NewGuid()), call InsertUpdateOrDeleteGraph(updated, existing) on the fresh context, SaveChangesAsync, and finally verify results using a new fresh context that Includes Tags so FK/catalog consistency is preserved and the test exercises detached-vs-tracked logic.src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs (1)
50-59: Consider adding timeout or retry for schema reset operations.
EnsureDeletedAsyncandEnsureCreatedAsynccan be slow or fail transiently when the SQL Server container is under load. For robustness in CI environments, consider wrapping with a command timeout or retry policy.This is a nice-to-have for CI reliability; the current implementation is functionally correct.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs` around lines 50 - 59, Wrap ResetSchemaAsync's calls to context.Database.EnsureDeletedAsync and EnsureCreatedAsync with a retry-and-timeout strategy to handle transient SQL Server container load; for example, acquire the DbContext from CreateContext(connectionString) and retry the EnsureDeletedAsync/EnsureCreatedAsync sequence a few times with short delays and per-call cancellation token timeouts, or use a Polly retry policy that retries on transient exceptions and enforces a per-attempt timeout—make sure to dispose the context between retries and surface a clear error if retries are exhausted.src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PayloadManyToManyStrategy.cs (1)
68-77: The reflection-based approach works correctly for standard collection types; consider aligning with PureManyToManyStrategy for consistency.
GetMethod("Add")successfully retrieves the method forList<T>,HashSet<T>, and other standard collection types—each has exactly one publicAddmethod. The current implementation is not broken.However, for consistency and maintainability, adopt the same interface-based lookup pattern used in
PureManyToManyStrategy.ExecuteCollectionOperation: check forIListfirst, then searchGetInterfaces()forICollection<>and retrieve the method from the interface rather than the concrete type.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PayloadManyToManyStrategy.cs` around lines 68 - 77, The AddToCollection method currently uses concrete-type reflection (GetMethod("Add")); change it to follow the pattern in PureManyToManyStrategy.ExecuteCollectionOperation: if navigation.CurrentValue implements IList use that directly, otherwise find an implemented generic ICollection<> via navigation.CurrentValue.GetType().GetInterfaces(), get the Add method from the ICollection<> interface (not the concrete type), and invoke that method with the item; keep the existing InvalidOperationException messages but replace the GetMethod("Add") lookup and invocation in AddToCollection with the IList/ICollection<> detection and interface-based Add invocation.src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cs (1)
29-42: Consider consistent error handling between tracked and detached key extraction.The tracked overload (
GetKeyValues(EntityEntry)) throwsInvalidOperationExceptionwhen the entity lacks a primary key, but the detached overload silently returns an empty array. This inconsistency could mask configuration errors.Consider throwing for consistency
public static object[] GetKeyValues(DbContext context, object entity) { var entityType = context.Model.FindEntityType(entity.GetType()); if (entityType is null) - return []; + throw new InvalidOperationException( + $"Entity type '{entity.GetType().Name}' is not part of the model."); var primaryKey = entityType.FindPrimaryKey(); if (primaryKey is null) - return []; + throw new InvalidOperationException( + $"Entity '{entityType.ClrType.Name}' does not define a primary key.");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cs` around lines 29 - 42, GetKeyValues(DbContext, object) currently returns an empty array when the entity type or primary key is missing; change it to mirror the tracked overload GetKeyValues(EntityEntry) by throwing an InvalidOperationException instead of returning [] when context.Model.FindEntityType(entity.GetType()) or entityType.FindPrimaryKey() is null, and keep the rest of the method (calling GetRequiredDetachedKeyValue and ToArray) the same so error handling is consistent between tracked and detached key extraction.src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/PureManyToManyContractTests.cs (1)
145-179: Verify intended behavior: updating shared entity through association.This test validates that updating a
TopicTaglabel through the association propagates the change. However, ifTag1is linked to multiple courses, this update affects all of them—which may or may not be the intended behavior.Consider documenting this behavior explicitly in either the test name or a comment, as it represents a potentially surprising side effect for API consumers.
Consider clarifying the test intent
[Fact] - public async Task Update_existing_related_entity_through_association_updates_entity_state() + public async Task Update_existing_related_entity_through_association_updates_entity_globally() { + // Note: This updates the shared TopicTag entity, affecting all courses linked to it. + // This is expected behavior for many-to-many relationships where the related entity + // is a shared principal, not an owned dependent. + // Arrange🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/PureManyToManyContractTests.cs` around lines 145 - 179, The test Update_existing_related_entity_through_association_updates_entity_state currently updates a shared TopicTag (TopicTag with Id SeedData.Tag1Id) via the association which will mutate that tag globally across all courses; update the test to make the intent explicit by either renaming the test to indicate shared-entity propagation (e.g., ..._updates_shared_entity_globally) or add a concise comment above the arrange/assert sections explaining that updating a TopicTag through InsertUpdateOrDeleteGraph will update the underlying TopicTag for all courses that reference it, and optionally add an extra assert that verifies another course referencing SeedData.Tag1Id also sees the changed Label to codify the observed behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/dotnet-core.yml:
- Around line 21-24: Replace mutable action tags with immutable commit SHAs:
update the uses entries for actions/checkout and actions/setup-dotnet (and the
other occurrences of those same action identifiers later in the file) so they
reference the full commit SHA instead of `@v6` or `@v5`; find the uses:
actions/checkout@v6 and uses: actions/setup-dotnet@v5 entries and pin each to
the corresponding full commit SHA from the GitHub repository release (e.g.,
actions/checkout@<full-commit-sha>) to prevent tag retargeting.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/PayloadAssociationContractTests.cs`:
- Around line 19-25: The test class PayloadAssociationContractTests is missing
an override of InitializeAsync which leads to shared schema state; add an async
override of InitializeAsync in the PayloadAssociationContractTests class that
resets/initializes the database/schema (matching the pattern used in other
integration test classes) and call any cleanup/setup helpers there, then remove
the per-test SeedData.SeedFullScenarioAsync calls from each test (including
Add_new_assignment_creates_association_entity_with_payload) so tests rely on the
centralized initialization and remain isolated.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/OptionalOneToOneContractTests.cs`:
- Around line 13-16: The test class OptionalOneToOneContractTests is missing the
collection attribute required for shared fixture lifecycle; add
[Collection(IntegrationTestCollection.Name)] above the
OptionalOneToOneContractTests class declaration so it uses the
IntegrationTestCollection fixture consistently with other integration tests.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/RequiredOneToOneContractTests.cs`:
- Around line 12-15: Add the missing test collection attribute to the
RequiredOneToOneContractTests class so the SqlServerContainerFixture is shared;
specifically, annotate the RequiredOneToOneContractTests class with
[Collection(IntegrationTestCollection.Name)] (same as
UnloadedNavigationMutationTests) so the test runner groups it with the
integration collection.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/Rejection/UnloadedNavigationMutationTests.cs`:
- Around line 14-17: The test class UnloadedNavigationMutationTests is missing
the collection attribute used for fixture sharing; add
[Collection(IntegrationTestCollection.Name)] above the
UnloadedNavigationMutationTests class declaration so it uses the same SQL Server
container fixture and test isolation as other tests that inherit
IntegrationTestBase (e.g., PayloadAssociationContractTests).
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SqlServerContainerFixture.cs`:
- Around line 11-12: The MsSqlContainer instantiation uses a mutable image tag
"mcr.microsoft.com/mssql/server:2022-latest" in the private field _container
(constructed via MsSqlBuilder), which can cause flaky tests; change it to a
fixed immutable tag like "mcr.microsoft.com/mssql/server:2022-CU10-ubuntu-20.04"
or make the image configurable by reading an environment variable (e.g.,
SQL_SERVER_IMAGE) with that immutable tag as the default; update the
MsSqlBuilder call that constructs _container to use the chosen value so CI is
deterministic while allowing opt-in overrides.
In
`@src/Diwink.Extensions.EntityFrameworkCore/Exceptions/GraphUpdateException.cs`:
- Around line 10-14: The GraphUpdateException constructor currently allows
null/whitespace for relationshipPath; update the GraphUpdateException(string
message, string relationshipPath) constructor to validate relationshipPath
(throw ArgumentNullException if null and ArgumentException if empty/whitespace)
before assigning to the RelationshipPath property so the exception always
carries a non-empty path for diagnostics.
In `@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cs`:
- Around line 44-50: KeysEqual currently calls keys1.SequenceEqual(keys2) which
uses object.Equals and will treat byte[] keys by reference, causing false
negatives for equal byte[] contents; update EntityKeyHelper.KeysEqual to
explicitly handle byte[] elements: check nulls and lengths first, then for each
index if both elements are byte[] compare their contents (e.g., call
Enumerable.SequenceEqual on cast (byte[]) arrays or use
StructuralComparisons.StructuralEqualityComparer), otherwise fall back to
object.Equals; ensure symmetric handling when one is byte[] and the other is
not.
In
`@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/GraphUpdateOrchestrator.cs`:
- Around line 280-296: IsPayloadJoinEntity currently only checks that the PK is
composite and that all PK properties are also FK properties; update the
implementation to match the docstring by also verifying the presence of at least
one payload property (a property on the IEntityType that is not part of the
primary key and is not present in any of the entity's foreign key property
lists). Concretely, in IsPayloadJoinEntity after computing primaryKey and
foreignKeys, compute the set of FK properties (union of
foreignKeys.SelectMany(fk => fk.Properties)), then check for any property in
entityType.GetProperties() that is not in primaryKey.Properties and not in that
FK set; only return true if primaryKey exists, has count >=2, all PK parts are
FKs, and such a non-key/non-FK property exists. Ensure method still returns
false for pure join entities.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OptionalOneToOneStrategy.cs`:
- Around line 18-52: Add explicit null-argument guards at the top of
DetachDependent, AttachDependent and ReplaceDependent: validate context and
existingNavigation in all three methods and throw ArgumentNullException if
either is null; additionally validate dependent in AttachDependent and
ReplaceDependent (throw ArgumentNullException if null) so callers get
deterministic, actionable failures instead of implicit dereference exceptions
when using DetachDependent, AttachDependent or ReplaceDependent.
---
Nitpick comments:
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/ManyToManySafetyContractTests.cs`:
- Around line 13-14: The [Collection(IntegrationTestCollection.Name)] attribute
on the ManyToManySafetyContractTests class is redundant because
IntegrationTestBase already declares it; remove the attribute from the
ManyToManySafetyContractTests class declaration so the class simply reads
"public class ManyToManySafetyContractTests : IntegrationTestBase", and run
tests to confirm no behavior change.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/PureManyToManyContractTests.cs`:
- Around line 145-179: The test
Update_existing_related_entity_through_association_updates_entity_state
currently updates a shared TopicTag (TopicTag with Id SeedData.Tag1Id) via the
association which will mutate that tag globally across all courses; update the
test to make the intent explicit by either renaming the test to indicate
shared-entity propagation (e.g., ..._updates_shared_entity_globally) or add a
concise comment above the arrange/assert sections explaining that updating a
TopicTag through InsertUpdateOrDeleteGraph will update the underlying TopicTag
for all courses that reference it, and optionally add an extra assert that
verifies another course referencing SeedData.Tag1Id also sees the changed Label
to codify the observed behavior.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/RequiredOneToOneContractTests.cs`:
- Around line 33-34: The local variable policyVersion (assigned from
existing.Policy!.PolicyVersion) is unused; either remove that assignment or
assert something with it. Locate RequiredOneToOneContractTests and the lines
referencing existing.Policy and policyVersion, then either delete the
policyVersion assignment, or replace it with an assertion using policyVersion
(for example assert the expected PolicyVersion from seed data against
existing.Policy!.PolicyVersion) so the variable is consumed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Diwink.Extensions.EntityFrameworkCore.Tests.Integration.csproj`:
- Around line 12-15: Replace the floating wildcard package versions with exact,
pinned versions to ensure deterministic CI restores: update the PackageReference
entries for FluentAssertions, Microsoft.EntityFrameworkCore.SqlServer, and
Testcontainers.MsSql (the elements with Include="FluentAssertions",
Include="Microsoft.EntityFrameworkCore.SqlServer",
Include="Testcontainers.MsSql") to use explicit version numbers instead of
"8.*", "10.*", and "4.*" respectively (leave Microsoft.NET.Test.Sdk as-is if
already pinned); commit the csproj change so CI always restores the specified
exact package versions.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs`:
- Around line 50-59: Wrap ResetSchemaAsync's calls to
context.Database.EnsureDeletedAsync and EnsureCreatedAsync with a
retry-and-timeout strategy to handle transient SQL Server container load; for
example, acquire the DbContext from CreateContext(connectionString) and retry
the EnsureDeletedAsync/EnsureCreatedAsync sequence a few times with short delays
and per-call cancellation token timeouts, or use a Polly retry policy that
retries on transient exceptions and enforces a per-attempt timeout—make sure to
dispose the context between retries and surface a clear error if retries are
exhausted.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cs`:
- Around line 84-125: The test Detects_removed_skip_navigation_entries uses a
single DbContext (CreateInMemoryContext) for arrange/act/assert which can let
the change tracker hide bugs; refactor it to follow the multi-context pattern
used in Detects_added_skip_navigation_entries by seeding data in one context
(ctx seed), then opening a fresh context to load existing via Include(c =>
c.Tags) into a tracked entity (existing), build an updated detached Course
object (updated) with the same CatalogId as the seeded course (do not call
CatalogId = Guid.NewGuid()), call InsertUpdateOrDeleteGraph(updated, existing)
on the fresh context, SaveChangesAsync, and finally verify results using a new
fresh context that Includes Tags so FK/catalog consistency is preserved and the
test exercises detached-vs-tracked logic.
In
`@src/Diwink.Extensions.EntityFrameworkCore/Exceptions/AmbiguousOwnershipSemanticsException.cs`:
- Around line 11-20: The constructor AmbiguousOwnershipSemanticsException should
validate the missingDetail parameter and reject null/whitespace to preserve
exception integrity; inside the AmbiguousOwnershipSemanticsException(string
relationshipPath, string missingDetail) constructor, check
string.IsNullOrWhiteSpace(missingDetail) and throw an ArgumentException or
ArgumentNullException with a clear message (including the parameter name
"missingDetail") before calling base, ensuring the public MissingDetail property
is only assigned valid non-empty text.
In
`@src/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cs`:
- Around line 11-20: The constructor PartialMutationNotAllowedException should
validate that the incoming unsupportedBranch is not null/empty/whitespace before
assigning; update the PartialMutationNotAllowedException(string
relationshipPath, string unsupportedBranch) constructor to check
string.IsNullOrWhiteSpace(unsupportedBranch) and throw an ArgumentException or
ArgumentNullException with a clear parameter name if invalid, then assign
UnsupportedBranch only after validation so the aggregated rejection message
never contains an empty value.
In
`@src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cs`:
- Around line 12-23: The UnsupportedNavigationMutatedException constructor
stores the unvalidated parameter relationshipType into the RelationshipType
property; validate that relationshipType is not null/empty (e.g., throw
ArgumentNullException or ArgumentException after trimming) before assigning it.
Update the UnsupportedNavigationMutatedException(string relationshipPath, string
relationshipType) constructor to check relationshipType (and relationshipPath if
desired) and throw an appropriate exception for invalid values, then assign
RelationshipType only after validation so error payloads remain dependable.
In
`@src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cs`:
- Around line 10-19: The constructor for UnsupportedRelationshipPatternException
stores patternIdentifier directly into the exception contract data without
validation; add a guard in the UnsupportedRelationshipPatternException
constructor to validate patternIdentifier (throw ArgumentNullException or
ArgumentException for null/empty/whitespace) before assigning PatternIdentifier
and before any contract-storage logic so consumers get a clear rejection
diagnostic; update the constructor that currently sets PatternIdentifier to
perform this check and throw a descriptive argument exception mentioning
"patternIdentifier" when invalid.
In `@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cs`:
- Around line 29-42: GetKeyValues(DbContext, object) currently returns an empty
array when the entity type or primary key is missing; change it to mirror the
tracked overload GetKeyValues(EntityEntry) by throwing an
InvalidOperationException instead of returning [] when
context.Model.FindEntityType(entity.GetType()) or entityType.FindPrimaryKey() is
null, and keep the rest of the method (calling GetRequiredDetachedKeyValue and
ToArray) the same so error handling is consistent between tracked and detached
key extraction.
In `@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs`:
- Line 16: The Errors property currently returns the mutable backing _errors
list directly; change the getter to return an immutable view (e.g. wrap _errors
with ReadOnlyCollection via _errors.AsReadOnly() or return a ReadOnlyCollection
cached field) so callers cannot cast back to List<T> and mutate state; keep
mutation confined to AddError and reference GraphUpdateException and
_errors/Errors/AddError to locate the change.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cs`:
- Around line 20-23: IsRequiredDependent currently dereferences
foreignKey.IsRequired without validation; add a null guard at the start of the
method (in the IsRequiredDependent method) to validate the foreignKey parameter
and throw an ArgumentNullException (include the parameter name) if it's null so
the method fails fast with an explicit error instead of producing an ambiguous
NRE.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PayloadManyToManyStrategy.cs`:
- Around line 68-77: The AddToCollection method currently uses concrete-type
reflection (GetMethod("Add")); change it to follow the pattern in
PureManyToManyStrategy.ExecuteCollectionOperation: if navigation.CurrentValue
implements IList use that directly, otherwise find an implemented generic
ICollection<> via navigation.CurrentValue.GetType().GetInterfaces(), get the Add
method from the ICollection<> interface (not the concrete type), and invoke that
method with the item; keep the existing InvalidOperationException messages but
replace the GetMethod("Add") lookup and invocation in AddToCollection with the
IList/ICollection<> detection and interface-based Add invocation.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cs`:
- Around line 48-75: context.Find can return an already-tracked entity whose
in-memory changes would be clobbered by the unconditional
context.Entry(knownEntity).CurrentValues.SetValues(updatedItem); modify the
logic inside the PureManyToManyStrategy flow to first inspect the tracked state
(via context.Entry(knownEntity).State) and only call SetValues when the entity
was not previously modified (e.g., State is Unchanged or Detached); otherwise
either merge selective properties or skip SetValues and just
AddToCollection(knownEntity) so you don’t overwrite local changes from the same
DbContext.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cs`:
- Around line 17-23: RequiredOneToOneStrategy.RemoveDependent currently
dereferences parameters without validation; add defensive argument guards at the
strategy boundary by validating both context and existingNavigation are not null
and throwing ArgumentNullException with appropriate param names (use
nameof(context) and nameof(existingNavigation)) at the top of the
RemoveDependent method before accessing existingNavigation.CurrentValue; keep
the existing null-check for existingNavigation.CurrentValue as-is.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 50fc7f3f-cc0d-4bf9-ad23-1bb27c47fb06
📒 Files selected for processing (73)
.github/workflows/dotnet-core.yml.gitignoreEfCore.UpdateGraph.slnREADME.mdsrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CourseConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CourseMentorAssignmentConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CoursePolicyConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/LearningCatalogConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorWorkspaceConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/TopicTagConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Diwink.Extensions.EntityFrameworkCore.TestModel.csprojsrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Entities/Course.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Entities/CourseMentorAssignment.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Entities/CoursePolicy.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Entities/LearningCatalog.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Entities/Mentor.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Entities/MentorWorkspace.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Entities/TopicTag.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/TestDbContext.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/ManyToManySafetyContractTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/PayloadAssociationContractTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/PureManyToManyContractTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/OptionalOneToOneContractTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/RequiredOneToOneContractTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/Rejection/PartialMutationNotAllowedTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/Rejection/UnloadedNavigationMutationTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/Rejection/UnsupportedRelationshipPatternTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Diwink.Extensions.EntityFrameworkCore.Tests.Integration.csprojsrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrapTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/IntegrationTestBase.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/IntegrationTestCollection.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedData.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SqlServerContainerFixture.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/Diwink.Extensions.EntityFrameworkCore.Tests.Unit.csprojsrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/DbContextExtensionsTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/NestedNavigationValidationTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/OperationGuardTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/UnsupportedRelationshipPatternTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/OneToOneOwnershipResolverTests.cssrc/Diwink.Extensions.EntityFrameworkCore/DbContextExtensions.cssrc/Diwink.Extensions.EntityFrameworkCore/Diwink.Extensions.EntityFrameworkCore.csprojsrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/AmbiguousOwnershipSemanticsException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/GraphUpdateException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnloadedNavigationMutationException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/GraphUpdateOrchestrator.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/RelatedEntityMutationService.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OptionalOneToOneStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PayloadManyToManyStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.cssrc/EFCore.UpdateGraph.slnxsrc/FakeModel/FakeModel.csprojsrc/FakeModel/Model.cssrc/UnitTests/FakeSchoolsDbContext.cssrc/UnitTests/FakeSchoolsDbContextFactory.cssrc/UnitTests/GraphUpdateTests.cssrc/UnitTests/Migrations/20200914083038_initialMigration.Designer.cssrc/UnitTests/Migrations/20200914083038_initialMigration.cssrc/UnitTests/Migrations/FakeSchoolsDbContextModelSnapshot.cssrc/UnitTests/TestHelpers.cssrc/UnitTests/UnitTests.csprojsrc/UnitTests/VariousTests.cssrc/UnitTests/appsettings.json
💤 Files with no reviewable changes (13)
- src/UnitTests/appsettings.json
- EfCore.UpdateGraph.sln
- src/FakeModel/FakeModel.csproj
- src/UnitTests/TestHelpers.cs
- src/UnitTests/FakeSchoolsDbContextFactory.cs
- src/UnitTests/Migrations/FakeSchoolsDbContextModelSnapshot.cs
- src/UnitTests/Migrations/20200914083038_initialMigration.Designer.cs
- src/UnitTests/VariousTests.cs
- src/FakeModel/Model.cs
- src/UnitTests/UnitTests.csproj
- src/UnitTests/GraphUpdateTests.cs
- src/UnitTests/FakeSchoolsDbContext.cs
- src/UnitTests/Migrations/20200914083038_initialMigration.cs
| [Fact] | ||
| public async Task Add_new_assignment_creates_association_entity_with_payload() | ||
| { | ||
| // Arrange | ||
| await using var seedCtx = CreateContext(); | ||
| await SeedData.SeedFullScenarioAsync(seedCtx); | ||
|
|
There was a problem hiding this comment.
Missing InitializeAsync override for test isolation.
Unlike other integration test classes, this test seeds data at the start of each test method (Line 23-24) without first resetting the schema. If tests run in parallel or in varying order, residual data from previous tests could cause failures.
Consider overriding InitializeAsync like other test classes:
Proposed fix
public class PayloadAssociationContractTests : IntegrationTestBase
{
public PayloadAssociationContractTests(SqlServerContainerFixture fixture)
: base(fixture) { }
+ public override async Task InitializeAsync()
+ {
+ await base.InitializeAsync();
+ await using var ctx = CreateContext();
+ await SeedData.SeedFullScenarioAsync(ctx);
+ }
+
[Fact]
public async Task Add_new_assignment_creates_association_entity_with_payload()
{
- // Arrange
- await using var seedCtx = CreateContext();
- await SeedData.SeedFullScenarioAsync(seedCtx);
-
await using var ctx = CreateContext();Then remove the seed calls from each test method.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/ManyToMany/PayloadAssociationContractTests.cs`
around lines 19 - 25, The test class PayloadAssociationContractTests is missing
an override of InitializeAsync which leads to shared schema state; add an async
override of InitializeAsync in the PayloadAssociationContractTests class that
resets/initializes the database/schema (matching the pattern used in other
integration test classes) and call any cleanup/setup helpers there, then remove
the per-test SeedData.SeedFullScenarioAsync calls from each test (including
Add_new_assignment_creates_association_entity_with_payload) so tests rely on the
centralized initialization and remain isolated.
| public class OptionalOneToOneContractTests : IntegrationTestBase | ||
| { | ||
| public OptionalOneToOneContractTests(SqlServerContainerFixture fixture) | ||
| : base(fixture) { } |
There was a problem hiding this comment.
Missing [Collection] attribute for consistent fixture sharing.
Add [Collection(IntegrationTestCollection.Name)] for consistency with other integration tests and proper fixture lifecycle management.
Proposed fix
+[Collection(IntegrationTestCollection.Name)]
public class OptionalOneToOneContractTests : IntegrationTestBase
{
public OptionalOneToOneContractTests(SqlServerContainerFixture fixture)
: base(fixture) { }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public class OptionalOneToOneContractTests : IntegrationTestBase | |
| { | |
| public OptionalOneToOneContractTests(SqlServerContainerFixture fixture) | |
| : base(fixture) { } | |
| [Collection(IntegrationTestCollection.Name)] | |
| public class OptionalOneToOneContractTests : IntegrationTestBase | |
| { | |
| public OptionalOneToOneContractTests(SqlServerContainerFixture fixture) | |
| : base(fixture) { } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/OptionalOneToOneContractTests.cs`
around lines 13 - 16, The test class OptionalOneToOneContractTests is missing
the collection attribute required for shared fixture lifecycle; add
[Collection(IntegrationTestCollection.Name)] above the
OptionalOneToOneContractTests class declaration so it uses the
IntegrationTestCollection fixture consistently with other integration tests.
| public class RequiredOneToOneContractTests : IntegrationTestBase | ||
| { | ||
| public RequiredOneToOneContractTests(SqlServerContainerFixture fixture) | ||
| : base(fixture) { } |
There was a problem hiding this comment.
Missing [Collection] attribute for test collection grouping.
Similar to UnloadedNavigationMutationTests, this class should have the [Collection(IntegrationTestCollection.Name)] attribute to properly share the SqlServerContainerFixture.
Proposed fix
+[Collection(IntegrationTestCollection.Name)]
public class RequiredOneToOneContractTests : IntegrationTestBase
{
public RequiredOneToOneContractTests(SqlServerContainerFixture fixture)
: base(fixture) { }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public class RequiredOneToOneContractTests : IntegrationTestBase | |
| { | |
| public RequiredOneToOneContractTests(SqlServerContainerFixture fixture) | |
| : base(fixture) { } | |
| [Collection(IntegrationTestCollection.Name)] | |
| public class RequiredOneToOneContractTests : IntegrationTestBase | |
| { | |
| public RequiredOneToOneContractTests(SqlServerContainerFixture fixture) | |
| : base(fixture) { } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/RequiredOneToOneContractTests.cs`
around lines 12 - 15, Add the missing test collection attribute to the
RequiredOneToOneContractTests class so the SqlServerContainerFixture is shared;
specifically, annotate the RequiredOneToOneContractTests class with
[Collection(IntegrationTestCollection.Name)] (same as
UnloadedNavigationMutationTests) so the test runner groups it with the
integration collection.
| public class UnloadedNavigationMutationTests : IntegrationTestBase | ||
| { | ||
| public UnloadedNavigationMutationTests(SqlServerContainerFixture fixture) | ||
| : base(fixture) { } |
There was a problem hiding this comment.
Missing [Collection] attribute for test isolation.
Other integration test classes in this PR (e.g., PayloadAssociationContractTests) use [Collection(IntegrationTestCollection.Name)] to share the SQL Server container fixture across tests. This class inherits from IntegrationTestBase but lacks the collection attribute, which may cause fixture lifecycle issues or parallel execution conflicts.
Proposed fix
+[Collection(IntegrationTestCollection.Name)]
public class UnloadedNavigationMutationTests : IntegrationTestBase
{
public UnloadedNavigationMutationTests(SqlServerContainerFixture fixture)
: base(fixture) { }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public class UnloadedNavigationMutationTests : IntegrationTestBase | |
| { | |
| public UnloadedNavigationMutationTests(SqlServerContainerFixture fixture) | |
| : base(fixture) { } | |
| [Collection(IntegrationTestCollection.Name)] | |
| public class UnloadedNavigationMutationTests : IntegrationTestBase | |
| { | |
| public UnloadedNavigationMutationTests(SqlServerContainerFixture fixture) | |
| : base(fixture) { } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/Rejection/UnloadedNavigationMutationTests.cs`
around lines 14 - 17, The test class UnloadedNavigationMutationTests is missing
the collection attribute used for fixture sharing; add
[Collection(IntegrationTestCollection.Name)] above the
UnloadedNavigationMutationTests class declaration so it uses the same SQL Server
container fixture and test isolation as other tests that inherit
IntegrationTestBase (e.g., PayloadAssociationContractTests).
| /// <summary> | ||
| /// Detects whether an entity type is a payload join entity (explicit many-to-many). | ||
| /// A payload join entity has a composite key where all key parts are also foreign keys, | ||
| /// AND it has additional non-key, non-FK properties (the payload). | ||
| /// </summary> | ||
| private static bool IsPayloadJoinEntity(IEntityType entityType) | ||
| { | ||
| var primaryKey = entityType.FindPrimaryKey(); | ||
| if (primaryKey is null || primaryKey.Properties.Count < 2) | ||
| return false; | ||
|
|
||
| var foreignKeys = entityType.GetForeignKeys().ToList(); | ||
| var allKeyPropsAreFk = primaryKey.Properties.All(keyProp => | ||
| foreignKeys.Any(fk => fk.Properties.Contains(keyProp))); | ||
|
|
||
| return allKeyPropsAreFk; | ||
| } |
There was a problem hiding this comment.
Documentation/implementation mismatch in IsPayloadJoinEntity.
The docstring states the method checks for "additional non-key, non-FK properties (the payload)", but the implementation only verifies that all PK properties are also FK properties—it does not verify the presence of actual payload properties.
This means the current implementation would classify pure join tables (without payload) as "payload" entities if they use composite FK keys. In practice, EF Core pure M:M uses ISkipNavigation (handled earlier), but this could cause confusion for explicit join entities without payload.
Align implementation with documentation
Either update the docstring to match implementation:
/// <summary>
- /// Detects whether an entity type is a payload join entity (explicit many-to-many).
- /// A payload join entity has a composite key where all key parts are also foreign keys,
- /// AND it has additional non-key, non-FK properties (the payload).
+ /// Detects whether an entity type is an explicit join entity (payload many-to-many).
+ /// An explicit join entity has a composite key where all key parts are also foreign keys.
/// </summary>Or add payload property validation:
return allKeyPropsAreFk;
+
+ // Check for payload properties (non-key, non-FK properties)
+ var fkProperties = foreignKeys.SelectMany(fk => fk.Properties).ToHashSet();
+ var hasPayload = entityType.GetProperties()
+ .Any(p => !primaryKey.Properties.Contains(p) && !fkProperties.Contains(p) && !p.IsShadowProperty());
+
+ return allKeyPropsAreFk && hasPayload;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/GraphUpdateOrchestrator.cs`
around lines 280 - 296, IsPayloadJoinEntity currently only checks that the PK is
composite and that all PK properties are also FK properties; update the
implementation to match the docstring by also verifying the presence of at least
one payload property (a property on the IEntityType that is not part of the
primary key and is not present in any of the entity's foreign key property
lists). Concretely, in IsPayloadJoinEntity after computing primaryKey and
foreignKeys, compute the set of FK properties (union of
foreignKeys.SelectMany(fk => fk.Properties)), then check for any property in
entityType.GetProperties() that is not in primaryKey.Properties and not in that
FK set; only return true if primaryKey exists, has count >=2, all PK parts are
FKs, and such a non-key/non-FK property exists. Ensure method still returns
false for pure join entities.
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @WahidBitar. * #13 (comment) The following files were modified: * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CourseConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CourseMentorAssignmentConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CoursePolicyConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/LearningCatalogConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorWorkspaceConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/TopicTagConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/TestDbContext.cs` * `src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs` * `src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/IntegrationTestBase.cs` * `src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedData.cs` * `src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SqlServerContainerFixture.cs` * `src/Diwink.Extensions.EntityFrameworkCore/DbContextExtensions.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/AmbiguousOwnershipSemanticsException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/GraphUpdateException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnloadedNavigationMutationException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cs` * `src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/GraphUpdateOrchestrator.cs` * `src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs` * `src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/RelatedEntityMutationService.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OptionalOneToOneStrategy.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PayloadManyToManyStrategy.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.cs`
|
Note Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it. Generating unit tests... This may take up to 20 minutes. |
|
✅ Created PR with unit tests: #15 |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
.github/workflows/dotnet-core.yml (1)
14-66: Optional: add workflow concurrency to reduce overlapping publish attempts.This is not a blocker, but concurrency control helps avoid duplicate runs on rapid successive pushes.
Suggested refinement
name: .NET CI + +concurrency: + group: dotnet-ci-${{ github.ref }} + cancel-in-progress: true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/dotnet-core.yml around lines 14 - 66, Add GitHub Actions concurrency to the publish job to prevent overlapping publish runs: inside the existing publish job (named "publish") add a concurrency block such as "concurrency: { group: 'publish-${{ github.ref }}', cancel-in-progress: true }" (or similar group key to suit your branch/tag naming) so new publish runs cancel in-progress ones and avoid duplicate NuGet pushes.src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs (1)
35-38: XML documentation is misleading for the multiple-errors case.The doc states "Throws the first recorded error if any exist" but when multiple errors are recorded, the method throws a
PartialMutationNotAllowedException(a different exception type), not the first error.📝 Suggested documentation fix
/// <summary> - /// Throws the first recorded error if any exist, enforcing all-or-nothing - /// rejection before mutations are applied to the change tracker. + /// Throws if any errors were recorded, enforcing all-or-nothing rejection + /// before mutations are applied to the change tracker. A single error is + /// rethrown directly; multiple errors are wrapped in a + /// <see cref="PartialMutationNotAllowedException"/>. /// </summary>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs` around lines 35 - 38, Update the XML doc on OperationGuard.ThrowIfErrors to accurately describe its behavior: explain that if one error is recorded the method rethrows that single recorded exception, but if multiple errors are recorded it throws a PartialMutationNotAllowedException containing aggregated error information; reference the OperationGuard class and the PartialMutationNotAllowedException type in the summary so the documentation matches the implemented behavior.src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cs (2)
199-249: Use a fresh verify context for assertion consistency.This test asserts using the same context used for arrange/act, while other tests use fresh verification contexts. Aligning this test to the same pattern reduces state-carryover risk and keeps semantics consistent across the suite.
Proposed structure alignment
public async Task Detects_added_payload_association_entities() { - // Arrange - await using var ctx = CreateInMemoryContext(); + var dbName = Guid.NewGuid().ToString(); + + // Arrange + await using var ctx = CreateInMemoryContext(dbName); var mentor = new Mentor { Id = Guid.NewGuid(), DisplayName = "M1", Status = "Active" }; var course = new Course { @@ ctx.InsertUpdateOrDeleteGraph(updated, existing); await ctx.SaveChangesAsync(); // Assert - var assignments = await ctx.CourseMentorAssignments + await using var verifyCtx = CreateInMemoryContext(dbName); + var assignments = await verifyCtx.CourseMentorAssignments .Where(a => a.CourseId == course.Id) .ToListAsync(); assignments.Should().HaveCount(1); assignments[0].Role.Should().Be("Lead"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cs` around lines 199 - 249, The test Detects_added_payload_association_entities uses the same DbContext (ctx) for Arrange/Act and Assert which can leak tracked state; change the assertion phase to use a fresh verification context created via CreateInMemoryContext() so you query persisted state cleanly. Specifically, after await ctx.SaveChangesAsync() and before assertions, dispose/stop using the original ctx and create a new var verifyCtx = CreateInMemoryContext(); then query verifyCtx.CourseMentorAssignments (instead of ctx.CourseMentorAssignments) to assert the created CourseMentorAssignment; keep usage of InsertUpdateOrDeleteGraph and existing test data unchanged.
74-81: Strengthen assertion to validate membership, not only count.Line 80 only checks cardinality; it can pass with the wrong tag identities. Assert the resulting tag IDs as well.
Proposed assertion hardening
var result = await verifyCtx.Courses .Include(c => c.Tags) .FirstAsync(c => c.Id == courseId); - result.Tags.Should().HaveCount(2); + result.Tags.Should().HaveCount(2); + result.Tags.Select(t => t.Id).Should().BeEquivalentTo([tag1Id, tag2Id]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cs` around lines 74 - 81, The test currently only asserts result.Tags.Should().HaveCount(2) which can hide wrong identities; update the assertion in ManyToManyDiffStrategyTests (inside the verify block using CreateInMemoryContext and verifyCtx.Courses.Include(c => c.Tags).FirstAsync(...)) to assert the tag identities too — e.g., collect result.Tags.Select(t => t.Id) and assert it matches the expected tag id set (use FluentAssertions' BeEquivalentTo or Should().Contain/OnlyContain with the known tagId variables used earlier in the test, e.g., expectedTagId1/expectedTagId2) so the test verifies membership not just count.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/EntityKeyHelperTests.cs`:
- Line 48: The declaration private sealed class UnmappedEntity; uses a
semicolon-only class declaration which can be invalid depending on C# language
version; change the UnmappedEntity declaration to have an explicit empty body
(replace the semicolon-only declaration with a class body using braces) so the
class compiles across all C# versions, updating the UnmappedEntity type used in
EntityKeyHelperTests accordingly.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cs`:
- Around line 141-196: The test
Linking_existing_related_entity_does_not_clobber_modified_tracked_state
currently seeds the course with Tags = [] so it never exercises the
"already-linked" clobber path; add a new test (or modify this one) that seeds
the persisted Course with the TopicTag already present in existing.Tags (e.g. in
the initial seed block set Course.Tags to a collection containing the TopicTag
with Id = tagId), then in the act block fetch existing via ctx.Courses.Include(c
=> c.Tags), modify the tracked tag (trackedTag.Label = "Locally Modified" and
assert EntityState.Modified), call ctx.InsertUpdateOrDeleteGraph(updated,
existing) with updated.Tags containing a detached copy (same Id but different
Label), SaveChangesAsync, and finally verify the stored tag label remains
"Locally Modified" to ensure InsertUpdateOrDeleteGraph does not clobber a
locally modified tracked related entity.
In `@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs`:
- Around line 47-52: The current code passes the first error path as the primary
relationshipPath and includes it again inside allPaths, causing a duplicate in
the exception message; change the allPaths assembly to exclude the primary path
(use _errors.Skip(1).Select(e => e.RelationshipPath) when building allPaths) and
pass the primary as before to PartialMutationNotAllowedException (or, if the
constructor expects a non-empty unsupportedBranch, fall back to the primary when
the skipped collection is empty) so the message shows the primary path once and
any additional unsupported branches separately.
---
Nitpick comments:
In @.github/workflows/dotnet-core.yml:
- Around line 14-66: Add GitHub Actions concurrency to the publish job to
prevent overlapping publish runs: inside the existing publish job (named
"publish") add a concurrency block such as "concurrency: { group: 'publish-${{
github.ref }}', cancel-in-progress: true }" (or similar group key to suit your
branch/tag naming) so new publish runs cancel in-progress ones and avoid
duplicate NuGet pushes.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cs`:
- Around line 199-249: The test Detects_added_payload_association_entities uses
the same DbContext (ctx) for Arrange/Act and Assert which can leak tracked
state; change the assertion phase to use a fresh verification context created
via CreateInMemoryContext() so you query persisted state cleanly. Specifically,
after await ctx.SaveChangesAsync() and before assertions, dispose/stop using the
original ctx and create a new var verifyCtx = CreateInMemoryContext(); then
query verifyCtx.CourseMentorAssignments (instead of ctx.CourseMentorAssignments)
to assert the created CourseMentorAssignment; keep usage of
InsertUpdateOrDeleteGraph and existing test data unchanged.
- Around line 74-81: The test currently only asserts
result.Tags.Should().HaveCount(2) which can hide wrong identities; update the
assertion in ManyToManyDiffStrategyTests (inside the verify block using
CreateInMemoryContext and verifyCtx.Courses.Include(c =>
c.Tags).FirstAsync(...)) to assert the tag identities too — e.g., collect
result.Tags.Select(t => t.Id) and assert it matches the expected tag id set (use
FluentAssertions' BeEquivalentTo or Should().Contain/OnlyContain with the known
tagId variables used earlier in the test, e.g., expectedTagId1/expectedTagId2)
so the test verifies membership not just count.
In `@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs`:
- Around line 35-38: Update the XML doc on OperationGuard.ThrowIfErrors to
accurately describe its behavior: explain that if one error is recorded the
method rethrows that single recorded exception, but if multiple errors are
recorded it throws a PartialMutationNotAllowedException containing aggregated
error information; reference the OperationGuard class and the
PartialMutationNotAllowedException type in the summary so the documentation
matches the implemented behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a3080411-44c4-4fcd-8d95-bce6e84080f5
📒 Files selected for processing (23)
.github/workflows/dotnet-core.ymlsrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/RequiredOneToOneContractTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Diwink.Extensions.EntityFrameworkCore.Tests.Integration.csprojsrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SqlServerContainerFixture.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/EntityKeyHelperTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/ExceptionValidationTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/OperationGuardTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/RelationshipClassificationTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/AmbiguousOwnershipSemanticsException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/GraphUpdateException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/GraphUpdateOrchestrator.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OptionalOneToOneStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PayloadManyToManyStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cs
✅ Files skipped from review due to trivial changes (11)
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/AmbiguousOwnershipSemanticsException.cs
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cs
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cs
- src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cs
- src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Diwink.Extensions.EntityFrameworkCore.Tests.Integration.csproj
- src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SqlServerContainerFixture.cs
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cs
- src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PayloadManyToManyStrategy.cs
- src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Contracts/OneToOne/RequiredOneToOneContractTests.cs
- src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/OperationGuardTests.cs
- src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cs
🚧 Files skipped from review as they are similar to previous changes (6)
- src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cs
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/GraphUpdateException.cs
- src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OptionalOneToOneStrategy.cs
- src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs
- src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cs
- src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/GraphUpdateOrchestrator.cs
| .WithMessage("*does not exist in the current DbContext model*"); | ||
| } | ||
|
|
||
| private sealed class UnmappedEntity; |
There was a problem hiding this comment.
Potential C# syntax incompatibility at Line 48 (class declaration with ;).
private sealed class UnmappedEntity; may fail to compile depending on the configured C# language version. Prefer an explicit empty body for compatibility.
Proposed fix
- private sealed class UnmappedEntity;
+ private sealed class UnmappedEntity
+ {
+ }Is `private sealed class UnmappedEntity;` valid C# syntax for regular class declarations? Please cite official Microsoft C# language documentation/spec and indicate which C# versions support it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/GraphDiff/EntityKeyHelperTests.cs`
at line 48, The declaration private sealed class UnmappedEntity; uses a
semicolon-only class declaration which can be invalid depending on C# language
version; change the UnmappedEntity declaration to have an explicit empty body
(replace the semicolon-only declaration with a class body using braces) so the
class compiles across all C# versions, updating the UnmappedEntity type used in
EntityKeyHelperTests accordingly.
| public async Task Linking_existing_related_entity_does_not_clobber_modified_tracked_state() | ||
| { | ||
| var dbName = Guid.NewGuid().ToString(); | ||
| var catalogId = Guid.NewGuid(); | ||
| var courseId = Guid.NewGuid(); | ||
| var tagId = Guid.NewGuid(); | ||
|
|
||
| { | ||
| await using var seedCtx = CreateInMemoryContext(dbName); | ||
| seedCtx.LearningCatalogs.Add(new LearningCatalog { Id = catalogId, Name = "Catalog" }); | ||
| seedCtx.TopicTags.Add(new TopicTag { Id = tagId, Label = "Persisted" }); | ||
| seedCtx.Courses.Add(new Course | ||
| { | ||
| Id = courseId, | ||
| CatalogId = catalogId, | ||
| Title = "Test", | ||
| Code = "T-001", | ||
| Tags = [] | ||
| }); | ||
| await seedCtx.SaveChangesAsync(); | ||
| } | ||
|
|
||
| { | ||
| await using var ctx = CreateInMemoryContext(dbName); | ||
| var existing = await ctx.Courses | ||
| .Include(c => c.Tags) | ||
| .FirstAsync(c => c.Id == courseId); | ||
|
|
||
| var trackedTag = await ctx.TopicTags.FirstAsync(t => t.Id == tagId); | ||
| trackedTag.Label = "Locally Modified"; | ||
| ctx.ChangeTracker.DetectChanges(); | ||
| ctx.Entry(trackedTag).State.Should().Be(EntityState.Modified); | ||
|
|
||
| var updated = new Course | ||
| { | ||
| Id = courseId, | ||
| CatalogId = catalogId, | ||
| Title = "Test", | ||
| Code = "T-001", | ||
| Tags = [new TopicTag { Id = tagId, Label = "Detached Copy" }] | ||
| }; | ||
|
|
||
| ctx.InsertUpdateOrDeleteGraph(updated, existing); | ||
| await ctx.SaveChangesAsync(); | ||
| } | ||
|
|
||
| { | ||
| await using var verifyCtx = CreateInMemoryContext(dbName); | ||
| var result = await verifyCtx.Courses | ||
| .Include(c => c.Tags) | ||
| .FirstAsync(c => c.Id == courseId); | ||
|
|
||
| result.Tags.Should().ContainSingle(t => t.Id == tagId); | ||
| result.Tags.Single().Label.Should().Be("Locally Modified"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Coverage gap: this test does not hit the already-linked clobber path.
Despite the name, Line 158 seeds Tags = [], so this only validates linking a pre-existing store entity. It does not validate the branch where the related entity is already in existing.Tags and locally modified before diff. Add a dedicated test for that case to prevent false confidence on tracked-state preservation.
Proposed additional test case
+ [Fact]
+ public async Task Existing_linked_related_entity_does_not_clobber_modified_tracked_state()
+ {
+ var dbName = Guid.NewGuid().ToString();
+ var catalogId = Guid.NewGuid();
+ var courseId = Guid.NewGuid();
+ var tagId = Guid.NewGuid();
+
+ await using (var seedCtx = CreateInMemoryContext(dbName))
+ {
+ seedCtx.LearningCatalogs.Add(new LearningCatalog { Id = catalogId, Name = "Catalog" });
+ var tag = new TopicTag { Id = tagId, Label = "Persisted" };
+ seedCtx.Courses.Add(new Course
+ {
+ Id = courseId,
+ CatalogId = catalogId,
+ Title = "Test",
+ Code = "T-001",
+ Tags = [tag]
+ });
+ await seedCtx.SaveChangesAsync();
+ }
+
+ await using (var ctx = CreateInMemoryContext(dbName))
+ {
+ var existing = await ctx.Courses.Include(c => c.Tags).FirstAsync(c => c.Id == courseId);
+ var trackedTag = existing.Tags.Single(t => t.Id == tagId);
+ trackedTag.Label = "Locally Modified";
+ ctx.ChangeTracker.DetectChanges();
+
+ var updated = new Course
+ {
+ Id = courseId,
+ CatalogId = catalogId,
+ Title = "Test",
+ Code = "T-001",
+ Tags = [new TopicTag { Id = tagId, Label = "Detached Copy" }]
+ };
+
+ ctx.InsertUpdateOrDeleteGraph(updated, existing);
+ await ctx.SaveChangesAsync();
+ }
+
+ await using (var verifyCtx = CreateInMemoryContext(dbName))
+ {
+ var result = await verifyCtx.Courses.Include(c => c.Tags).FirstAsync(c => c.Id == courseId);
+ result.Tags.Single().Label.Should().Be("Locally Modified");
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public async Task Linking_existing_related_entity_does_not_clobber_modified_tracked_state() | |
| { | |
| var dbName = Guid.NewGuid().ToString(); | |
| var catalogId = Guid.NewGuid(); | |
| var courseId = Guid.NewGuid(); | |
| var tagId = Guid.NewGuid(); | |
| { | |
| await using var seedCtx = CreateInMemoryContext(dbName); | |
| seedCtx.LearningCatalogs.Add(new LearningCatalog { Id = catalogId, Name = "Catalog" }); | |
| seedCtx.TopicTags.Add(new TopicTag { Id = tagId, Label = "Persisted" }); | |
| seedCtx.Courses.Add(new Course | |
| { | |
| Id = courseId, | |
| CatalogId = catalogId, | |
| Title = "Test", | |
| Code = "T-001", | |
| Tags = [] | |
| }); | |
| await seedCtx.SaveChangesAsync(); | |
| } | |
| { | |
| await using var ctx = CreateInMemoryContext(dbName); | |
| var existing = await ctx.Courses | |
| .Include(c => c.Tags) | |
| .FirstAsync(c => c.Id == courseId); | |
| var trackedTag = await ctx.TopicTags.FirstAsync(t => t.Id == tagId); | |
| trackedTag.Label = "Locally Modified"; | |
| ctx.ChangeTracker.DetectChanges(); | |
| ctx.Entry(trackedTag).State.Should().Be(EntityState.Modified); | |
| var updated = new Course | |
| { | |
| Id = courseId, | |
| CatalogId = catalogId, | |
| Title = "Test", | |
| Code = "T-001", | |
| Tags = [new TopicTag { Id = tagId, Label = "Detached Copy" }] | |
| }; | |
| ctx.InsertUpdateOrDeleteGraph(updated, existing); | |
| await ctx.SaveChangesAsync(); | |
| } | |
| { | |
| await using var verifyCtx = CreateInMemoryContext(dbName); | |
| var result = await verifyCtx.Courses | |
| .Include(c => c.Tags) | |
| .FirstAsync(c => c.Id == courseId); | |
| result.Tags.Should().ContainSingle(t => t.Id == tagId); | |
| result.Tags.Single().Label.Should().Be("Locally Modified"); | |
| } | |
| } | |
| public async Task Linking_existing_related_entity_does_not_clobber_modified_tracked_state() | |
| { | |
| var dbName = Guid.NewGuid().ToString(); | |
| var catalogId = Guid.NewGuid(); | |
| var courseId = Guid.NewGuid(); | |
| var tagId = Guid.NewGuid(); | |
| { | |
| await using var seedCtx = CreateInMemoryContext(dbName); | |
| seedCtx.LearningCatalogs.Add(new LearningCatalog { Id = catalogId, Name = "Catalog" }); | |
| seedCtx.TopicTags.Add(new TopicTag { Id = tagId, Label = "Persisted" }); | |
| seedCtx.Courses.Add(new Course | |
| { | |
| Id = courseId, | |
| CatalogId = catalogId, | |
| Title = "Test", | |
| Code = "T-001", | |
| Tags = [] | |
| }); | |
| await seedCtx.SaveChangesAsync(); | |
| } | |
| { | |
| await using var ctx = CreateInMemoryContext(dbName); | |
| var existing = await ctx.Courses | |
| .Include(c => c.Tags) | |
| .FirstAsync(c => c.Id == courseId); | |
| var trackedTag = await ctx.TopicTags.FirstAsync(t => t.Id == tagId); | |
| trackedTag.Label = "Locally Modified"; | |
| ctx.ChangeTracker.DetectChanges(); | |
| ctx.Entry(trackedTag).State.Should().Be(EntityState.Modified); | |
| var updated = new Course | |
| { | |
| Id = courseId, | |
| CatalogId = catalogId, | |
| Title = "Test", | |
| Code = "T-001", | |
| Tags = [new TopicTag { Id = tagId, Label = "Detached Copy" }] | |
| }; | |
| ctx.InsertUpdateOrDeleteGraph(updated, existing); | |
| await ctx.SaveChangesAsync(); | |
| } | |
| { | |
| await using var verifyCtx = CreateInMemoryContext(dbName); | |
| var result = await verifyCtx.Courses | |
| .Include(c => c.Tags) | |
| .FirstAsync(c => c.Id == courseId); | |
| result.Tags.Should().ContainSingle(t => t.Id == tagId); | |
| result.Tags.Single().Label.Should().Be("Locally Modified"); | |
| } | |
| } | |
| [Fact] | |
| public async Task Existing_linked_related_entity_does_not_clobber_modified_tracked_state() | |
| { | |
| var dbName = Guid.NewGuid().ToString(); | |
| var catalogId = Guid.NewGuid(); | |
| var courseId = Guid.NewGuid(); | |
| var tagId = Guid.NewGuid(); | |
| await using (var seedCtx = CreateInMemoryContext(dbName)) | |
| { | |
| seedCtx.LearningCatalogs.Add(new LearningCatalog { Id = catalogId, Name = "Catalog" }); | |
| var tag = new TopicTag { Id = tagId, Label = "Persisted" }; | |
| seedCtx.Courses.Add(new Course | |
| { | |
| Id = courseId, | |
| CatalogId = catalogId, | |
| Title = "Test", | |
| Code = "T-001", | |
| Tags = [tag] | |
| }); | |
| await seedCtx.SaveChangesAsync(); | |
| } | |
| await using (var ctx = CreateInMemoryContext(dbName)) | |
| { | |
| var existing = await ctx.Courses.Include(c => c.Tags).FirstAsync(c => c.Id == courseId); | |
| var trackedTag = existing.Tags.Single(t => t.Id == tagId); | |
| trackedTag.Label = "Locally Modified"; | |
| ctx.ChangeTracker.DetectChanges(); | |
| var updated = new Course | |
| { | |
| Id = courseId, | |
| CatalogId = catalogId, | |
| Title = "Test", | |
| Code = "T-001", | |
| Tags = [new TopicTag { Id = tagId, Label = "Detached Copy" }] | |
| }; | |
| ctx.InsertUpdateOrDeleteGraph(updated, existing); | |
| await ctx.SaveChangesAsync(); | |
| } | |
| await using (var verifyCtx = CreateInMemoryContext(dbName)) | |
| { | |
| var result = await verifyCtx.Courses.Include(c => c.Tags).FirstAsync(c => c.Id == courseId); | |
| result.Tags.Single().Label.Should().Be("Locally Modified"); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cs`
around lines 141 - 196, The test
Linking_existing_related_entity_does_not_clobber_modified_tracked_state
currently seeds the course with Tags = [] so it never exercises the
"already-linked" clobber path; add a new test (or modify this one) that seeds
the persisted Course with the TopicTag already present in existing.Tags (e.g. in
the initial seed block set Course.Tags to a collection containing the TopicTag
with Id = tagId), then in the act block fetch existing via ctx.Courses.Include(c
=> c.Tags), modify the tracked tag (trackedTag.Label = "Locally Modified" and
assert EntityState.Modified), call ctx.InsertUpdateOrDeleteGraph(updated,
existing) with updated.Tags containing a detached copy (same Id but different
Label), SaveChangesAsync, and finally verify the stored tag label remains
"Locally Modified" to ensure InsertUpdateOrDeleteGraph does not clobber a
locally modified tracked related entity.
| // When multiple errors exist, wrap in PartialMutationNotAllowed | ||
| // with all unsupported branches listed for diagnostic clarity | ||
| var allPaths = string.Join(", ", _errors.Select(e => e.RelationshipPath)); | ||
| throw new PartialMutationNotAllowedException( | ||
| _errors[0].RelationshipPath, | ||
| allPaths); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect the PartialMutationNotAllowedException constructor to understand how
# the two path parameters are used in the exception message.
ast-grep --pattern $'class PartialMutationNotAllowedException {
$$$
}'
# Also check for message formatting
rg -n -A10 'class PartialMutationNotAllowedException'Repository: WahidBitar/EF-Core-Simple-Graph-Update
Length of output: 1582
Clarify intent of passing both the first path and all paths to the exception.
The constructor accepts relationshipPath (passed to base) and unsupportedBranch (embedded in message: "...detected at '{unsupportedBranch}'..."). Since allPaths includes the first path, it already contains _errors[0].RelationshipPath. Confirm whether:
- Both parameters are intentional (primary path for base exception, all paths in message), or
- The first path should be excluded from
allPathsto avoid duplication in the displayed message.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs`
around lines 47 - 52, The current code passes the first error path as the
primary relationshipPath and includes it again inside allPaths, causing a
duplicate in the exception message; change the allPaths assembly to exclude the
primary path (use _errors.Skip(1).Select(e => e.RelationshipPath) when building
allPaths) and pass the primary as before to PartialMutationNotAllowedException
(or, if the constructor expects a non-empty unsupportedBranch, fall back to the
primary when the skipped collection is empty) so the message shows the primary
path once and any additional unsupported branches separately.
… into 001-rebuild-graph-update
…and graph update exceptions Enhance test coverage by adding integration tests for the SeedData helper to validate distinct IDs, entity relationships, and in-memory database seeding. Introduce unit tests for `GraphUpdateException` derived types, ensuring robust constructor validations, message formatting, and inheritance chain testing. Remove deprecated `ManyToManyDiffStrategyTests` in favor of the new testing structure. Update dependencies in project files to include EF Core InMemory provider for test scenarios.
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (3)
src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs (1)
121-125: Consider narrowing the retryable exception scope.
InvalidOperationExceptionis quite broad and could mask genuine programming errors. For SQL Server connectivity issues,SqlExceptiontypically covers transient failures. Consider whetherInvalidOperationExceptionis truly needed here, or if specific subtypes/messages should be checked.That said, for test infrastructure against containerized SQL Server, this broad catch may be pragmatic given connection pool edge cases during container startup.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs` around lines 121 - 125, The IsRetryableSchemaException method currently treats every InvalidOperationException as retryable which is too broad; narrow it by removing the blanket InvalidOperationException check and instead only treat InvalidOperationException as retryable when it matches known transient scenarios (e.g., specific message/errno/inner-exception patterns caused by SQL connectivity or pool initialization), or drop it entirely and rely on SqlException/TimeoutException plus the cancellation/OperationCanceledException branch; update IsRetryableSchemaException to check exception is SqlException or TimeoutException or (cancellationSource.IsCancellationRequested && exception is OperationCanceledException), and if you must keep InvalidOperationException, add targeted checks for known transient messages/inner-exceptions in that function (use the exception.Message or exception.InnerException type to identify transient pool/startup errors).src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cs (1)
55-83: Consider batchingcontext.Findcalls for large collections.Each unlinked entity in
updatedItemstriggers acontext.Find()call (Line 63), which may query the database if the entity isn't tracked. For collections with many new links, this could cause N+1 query behavior.For typical use cases with small collections, this is acceptable. If performance becomes a concern with larger graphs, consider pre-loading candidate entities in a single query.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cs` around lines 55 - 83, The per-item context.Find call in PureManyToManyStrategy (see usage of context.Find, updatedItem and updatedKeys) can cause N+1 queries for large updatedItems collections; modify the logic to batch-load candidate entities before the loop by collecting all primary key values for the target entity type, querying the DbSet for those keys in a single query to build a lookup (dictionary by key), then inside the loop use the lookup to resolve knownEntity and only fall back to context.Find or Add when a key is missing; keep ApplyValuesIfNotModified and AddToCollection usage but replace the repeated context.Find calls with the preloaded lookup to avoid many DB roundtrips.src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedDataStructureTests.cs (1)
53-57: Rename this test or assert the “catalog prefix” invariant.The method name/comment says we're checking a shared catalog prefix, but the assertion only verifies that the two IDs differ. That mismatch makes the test intent harder to trust when it fails.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedDataStructureTests.cs` around lines 53 - 57, The test Course1Id_and_Course2Id_share_same_catalog_prefix currently only asserts SeedData.Course1Id.Should().NotBe(SeedData.Course2Id);—either rename the test to reflect that it only verifies inequality, or add an assertion that checks the catalog prefix invariant: extract the catalog prefix from SeedData.Course1Id and SeedData.Course2Id (using the same parsing logic used in your ID type) and assert they are equal while still asserting the IDs are not equal; update the test name if you choose to keep only the inequality check (e.g., CourseIds_are_distinct) or keep the existing name and add the prefix equality assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs`:
- Around line 53-58: The XML documentation for the DatabaseBootstrap method
contains a duplicated and unclosed <summary> tag (the first <summary> is not
closed), causing malformed XML comments; update the doc on the
DatabaseBootstrap/EnsureDatabaseSchemaCreated member so there is a single
well-formed <summary>...</summary> block (remove the extra <summary> line or
close it properly) and ensure the param description remains inside a proper
<param name="connectionString">...</param> tag.
- Around line 18-24: The XML doc for the method in DatabaseBootstrap contains
duplicate/malformed <summary> tags; open the documentation for the method that
"Creates a TestDbContext configured to use SQL Server..." (the method in class
DatabaseBootstrap that returns a TestDbContext and accepts a connectionString)
and merge the two <summary> blocks into a single well-formed
<summary>...</summary> describing the behavior, then ensure the existing <param>
and <returns> tags remain intact and properly closed so the XML doc compiles
without CS1570.
- Around line 67-73: The XML doc for the database-reset method contains a
duplicated and unclosed <summary> tag; fix the XML by consolidating the two
summary lines into a single well-formed XML comment (one <summary> ...
</summary>) for the method in DatabaseBootstrap (the method that "Deletes and
recreates the test database schema for the given connection string"), ensuring
the opening <summary> is closed and no duplicate <summary> tag remains so XML
documentation parses correctly.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedData.cs`:
- Around line 23-28: The XML doc for the seeding method in the SeedData class is
malformed due to a duplicate opening <summary> tag; fix it by consolidating the
two <summary> entries into a single well-formed XML summary block (one opening
<summary>, the combined description, then a single closing </summary>), and
ensure the <param name="context"> tag remains outside that summary and correctly
formed; update the comment above the seeding method in SeedData.cs so XML doc
generation warnings are resolved.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/Exceptions/GraphUpdateExceptionTests.cs`:
- Around line 223-229: The test currently asserts that
UnsupportedNavigationMutatedException(string.Empty, string.Empty) stores empty
strings, but per GraphUpdateException validation it should reject an empty
relationshipPath; update the test to assert that constructing
UnsupportedNavigationMutatedException with an empty relationshipPath throws the
appropriate validation exception (e.g., ArgumentException or the specific
validation exception used by GraphUpdateException) instead of expecting
successful storage; reference the UnsupportedNavigationMutatedException
constructor in the assertion.
In `@src/Diwink.Extensions.EntityFrameworkCore/DbContextExtensions.cs`:
- Around line 12-35: Remove the duplicate XML doc block that repeats <summary>
and <param> tags for the extension method in DbContextExtensions (the doc
referencing updatedEntity and existingEntity) so only a single well-formed
documentation block remains; keep one <summary> describing synchronization, one
set of <typeparam name="T">, <param name="context">, <param
name="updatedEntity">, <param name="existingEntity">, <returns>, and <exception>
tags, and delete the redundant second <summary> and duplicated <param> entries
to produce valid XML documentation for the method.
In
`@src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/GraphUpdateOrchestrator.cs`:
- Around line 369-419: HasMutations currently only checks top-level key equality
and scalar differences (via HasScalarDifferences) so nested navigation changes
can be missed; update HasMutations to detect mutations inside matched entities'
navigations as well by, after finding a matching item
(EntityKeyHelper.FindByKey) and confirming keys equal, enumerating that entity's
NavigationEntries (from context.Entry(match).Navigations or the navigation
metadata) and calling the same mutation check recursively (reusing HasMutations
/ TryGetUpdatedNavigationValue) for each child navigation; do this for both
CollectionEntry item comparisons and the ReferenceEntry path so any
nested/unsupported-branch changes are detected and cause HasMutations to return
true.
- Around line 139-153: The mutation detection currently uses
updatedEntity.GetType().GetProperty(navMetadata.Name) which misses field-backed
navigations; update HasUnloadedMutationAttempt to read the navigation value via
navMetadata.PropertyInfo if not null (use PropertyInfo.GetValue on
updatedEntity) and otherwise use navMetadata.FieldInfo (FieldInfo.GetValue) as a
fallback, then preserve the existing null checks and IEnumerable<object>
collection any() logic; similarly apply the same pattern wherever
GetProperty(navMetadata.Name) is used to ensure both property and field-backed
navigations are handled.
- Around line 244-257: The current branch only handles key-mismatches for
OptionalOneToOne; add a similar check for
NavigationClassification.RequiredOneToOne so we don't attempt to update a
tracked dependent with a different key. Specifically, inside the block where
updatedValue and existingValue are non-null, check if classification ==
NavigationClassification.RequiredOneToOne and !ReferenceKeysMatch(context,
existingValue, updatedValue), and in that case invoke the same detach/replace
flow (e.g., call OptionalOneToOneStrategy.ReplaceDependent(context,
existingNavigation, updatedValue) or a new
RequiredOneToOneStrategy.ReplaceDependent if you prefer) and return, so scalar
updates via RelatedEntityMutationService.UpdateScalarProperties are not
attempted on a mismatched required dependent.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cs`:
- Around line 12-23: Fix the malformed XML doc block in
OneToOneOwnershipResolver: remove the duplicate <summary> entry and close the
missing </returns> so the doc has a single <summary> describing the method that
determines whether the dependent end of a one-to-one relationship is required
(the method that evaluates the EF Core ForeignKey parameter), a single <param
name="foreignKey"> tag, and a properly closed <returns> tag describing
true/false behavior; ensure the cleaned-up XML sits immediately above the method
that inspects the foreign key metadata.
- Around line 24-28: Remove the dead helper method IsRequiredDependent from
OneToOneOwnershipResolver: delete the public static bool
IsRequiredDependent(IForeignKey foreignKey) method (including its
ArgumentNullException.ThrowIfNull and return foreignKey.IsRequired) since
GraphUpdateOrchestrator.ClassifyNavigation already reads foreignKey.IsRequired
and there are no call sites; if intended for public API compatibility, instead
mark it obsolete with an explanatory message and redirect callers to use
foreignKey.IsRequired.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cs`:
- Around line 13-20: Fix the malformed XML doc on RequiredOneToOneStrategy by
closing the first <summary> tag and removing the duplicate summary block so the
method-level documentation is a single well-formed <summary> describing that it
marks the dependent referenced by existingNavigation for deletion; ensure the
param tags remain intact for context and parameters (refer to the
RequiredOneToOneStrategy class and the method that accepts DbContext and
existingNavigation) so the XML comments validate correctly.
In `@src/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.cs`:
- Around line 63-71: The XML documentation for the method in NavigationLoadGuard
(ValidateNavigationLoaded) contains a typo: an opening <summary> tag is used
twice instead of closing the first; replace the second "<summary>" with a
closing "</summary>" (or remove the duplicate) so the XML doc is well-formed and
the summary reads: "Validates that a specific navigation is loaded before
attempting mutation on it. Throws <see
cref=\"UnloadedNavigationMutationException\"/> if not." Ensure the parameter and
exception XML tags remain unchanged.
- Around line 14-21: The XML doc for NavigationLoadGuard has malformed/unclosed
<summary> tags (duplicate opening tags at the top comment) — consolidate the two
<summary> blocks into a single well-formed <summary> and ensure each opening
<summary> has a matching </summary>, then update the surrounding XML (the <param
name="existingEntry"> and <exception cref="UnloadedNavigationMutationException">
lines) so they remain inside the single summary/remarks block; locate the
comment above the NavigationLoadGuard declaration and fix the tags accordingly.
- Around line 1-3: The file NavigationLoadGuard.cs is missing the System.Linq
namespace causing the call to collectionEntry.CurrentValue.Cast<object>() to not
compile; add the using System.Linq; directive at the top of the file (alongside
existing usings) so the Enumerable.Cast<T>() extension is available for code in
the NavigationLoadGuard class and any methods referencing
collectionEntry.CurrentValue.Cast<object>().
---
Nitpick comments:
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs`:
- Around line 121-125: The IsRetryableSchemaException method currently treats
every InvalidOperationException as retryable which is too broad; narrow it by
removing the blanket InvalidOperationException check and instead only treat
InvalidOperationException as retryable when it matches known transient scenarios
(e.g., specific message/errno/inner-exception patterns caused by SQL
connectivity or pool initialization), or drop it entirely and rely on
SqlException/TimeoutException plus the cancellation/OperationCanceledException
branch; update IsRetryableSchemaException to check exception is SqlException or
TimeoutException or (cancellationSource.IsCancellationRequested && exception is
OperationCanceledException), and if you must keep InvalidOperationException, add
targeted checks for known transient messages/inner-exceptions in that function
(use the exception.Message or exception.InnerException type to identify
transient pool/startup errors).
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedDataStructureTests.cs`:
- Around line 53-57: The test Course1Id_and_Course2Id_share_same_catalog_prefix
currently only asserts
SeedData.Course1Id.Should().NotBe(SeedData.Course2Id);—either rename the test to
reflect that it only verifies inequality, or add an assertion that checks the
catalog prefix invariant: extract the catalog prefix from SeedData.Course1Id and
SeedData.Course2Id (using the same parsing logic used in your ID type) and
assert they are equal while still asserting the IDs are not equal; update the
test name if you choose to keep only the inequality check (e.g.,
CourseIds_are_distinct) or keep the existing name and add the prefix equality
assertion.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cs`:
- Around line 55-83: The per-item context.Find call in PureManyToManyStrategy
(see usage of context.Find, updatedItem and updatedKeys) can cause N+1 queries
for large updatedItems collections; modify the logic to batch-load candidate
entities before the loop by collecting all primary key values for the target
entity type, querying the DbSet for those keys in a single query to build a
lookup (dictionary by key), then inside the loop use the lookup to resolve
knownEntity and only fall back to context.Find or Add when a key is missing;
keep ApplyValuesIfNotModified and AddToCollection usage but replace the repeated
context.Find calls with the preloaded lookup to avoid many DB roundtrips.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d43ba35b-6820-46dc-8c68-ca2a037a911f
📒 Files selected for processing (33)
src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CourseConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CourseMentorAssignmentConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CoursePolicyConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/LearningCatalogConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorWorkspaceConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/TopicTagConfiguration.cssrc/Diwink.Extensions.EntityFrameworkCore.TestModel/TestDbContext.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Diwink.Extensions.EntityFrameworkCore.Tests.Integration.csprojsrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/IntegrationTestBase.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedData.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedDataStructureTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SqlServerContainerFixture.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/Exceptions/GraphUpdateExceptionTests.cssrc/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/RelationshipSemantics/ManyToManyDiffStrategyTests.cssrc/Diwink.Extensions.EntityFrameworkCore/DbContextExtensions.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/AmbiguousOwnershipSemanticsException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/GraphUpdateException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnloadedNavigationMutationException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cssrc/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/GraphUpdateOrchestrator.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cssrc/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/RelatedEntityMutationService.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OptionalOneToOneStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PayloadManyToManyStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cssrc/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.cs
✅ Files skipped from review due to trivial changes (7)
- src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorWorkspaceConfiguration.cs
- src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/TopicTagConfiguration.cs
- src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorConfiguration.cs
- src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Diwink.Extensions.EntityFrameworkCore.Tests.Integration.csproj
- src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/RelatedEntityMutationService.cs
- src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SqlServerContainerFixture.cs
- src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OptionalOneToOneStrategy.cs
🚧 Files skipped from review as they are similar to previous changes (10)
- src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CoursePolicyConfiguration.cs
- src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/LearningCatalogConfiguration.cs
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cs
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cs
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnloadedNavigationMutationException.cs
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/GraphUpdateException.cs
- src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/IntegrationTestBase.cs
- src/Diwink.Extensions.EntityFrameworkCore.TestModel/TestDbContext.cs
- src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cs
- src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs
| /// <summary> | ||
| /// Creates a fresh DbContext pointing at the container and ensures the schema exists. | ||
| /// <summary> | ||
| /// Creates a TestDbContext configured to use SQL Server with a test-specific connection string. | ||
| /// </summary> | ||
| /// <param name="connectionString">Base SQL Server connection string; if its initial catalog is empty or equals "master", the catalog will be replaced with the test database name.</param> | ||
| /// <returns>A new TestDbContext configured with the adjusted connection string.</returns> |
There was a problem hiding this comment.
Malformed XML documentation - duplicate <summary> tags.
Lines 18-20 contain an incomplete summary block that isn't closed before another <summary> starts on line 21. This will generate compiler warning CS1570.
📝 Proposed fix
- /// <summary>
- /// Creates a fresh DbContext pointing at the container and ensures the schema exists.
- /// <summary>
/// <summary>
/// Creates a TestDbContext configured to use SQL Server with a test-specific connection string.
/// </summary>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// <summary> | |
| /// Creates a fresh DbContext pointing at the container and ensures the schema exists. | |
| /// <summary> | |
| /// Creates a TestDbContext configured to use SQL Server with a test-specific connection string. | |
| /// </summary> | |
| /// <param name="connectionString">Base SQL Server connection string; if its initial catalog is empty or equals "master", the catalog will be replaced with the test database name.</param> | |
| /// <returns>A new TestDbContext configured with the adjusted connection string.</returns> | |
| /// <summary> | |
| /// Creates a TestDbContext configured to use SQL Server with a test-specific connection string. | |
| /// </summary> | |
| /// <param name="connectionString">Base SQL Server connection string; if its initial catalog is empty or equals "master", the catalog will be replaced with the test database name.</param> | |
| /// <returns>A new TestDbContext configured with the adjusted connection string.</returns> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs`
around lines 18 - 24, The XML doc for the method in DatabaseBootstrap contains
duplicate/malformed <summary> tags; open the documentation for the method that
"Creates a TestDbContext configured to use SQL Server..." (the method in class
DatabaseBootstrap that returns a TestDbContext and accepts a connectionString)
and merge the two <summary> blocks into a single well-formed
<summary>...</summary> describing the behavior, then ensure the existing <param>
and <returns> tags remain intact and properly closed so the XML doc compiles
without CS1570.
| /// <summary> | ||
| /// Ensures the database schema is created. Called once per test collection. | ||
| /// <summary> | ||
| /// Ensures the test database schema exists for the provided SQL Server connection string. | ||
| /// </summary> | ||
| /// <param name="connectionString">SQL Server connection string. If the Initial Catalog is empty or "master", the configured test database name will be used.</param> |
There was a problem hiding this comment.
Malformed XML documentation - same pattern as above.
Lines 53-55 have the same unclosed <summary> issue.
📝 Proposed fix
- /// <summary>
- /// Ensures the database schema is created. Called once per test collection.
- /// <summary>
/// <summary>
/// Ensures the test database schema exists for the provided SQL Server connection string.
/// </summary>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs`
around lines 53 - 58, The XML documentation for the DatabaseBootstrap method
contains a duplicated and unclosed <summary> tag (the first <summary> is not
closed), causing malformed XML comments; update the doc on the
DatabaseBootstrap/EnsureDatabaseSchemaCreated member so there is a single
well-formed <summary>...</summary> block (remove the extra <summary> line or
close it properly) and ensure the param description remains inside a proper
<param name="connectionString">...</param> tag.
| /// <summary> | ||
| /// Drops and recreates the database schema. Used for test isolation | ||
| /// when a test needs a guaranteed clean slate. | ||
| /// <summary> | ||
| /// Deletes and recreates the test database schema for the given connection string. | ||
| /// </summary> | ||
| /// <param name="connectionString">The database connection string used to locate the server; if the connection string has no catalog or specifies "master", the configured test database name will be used.</param> |
There was a problem hiding this comment.
Malformed XML documentation - same pattern.
Lines 67-70 have the same unclosed <summary> issue.
📝 Proposed fix
- /// <summary>
- /// Drops and recreates the database schema. Used for test isolation
- /// when a test needs a guaranteed clean slate.
- /// <summary>
/// <summary>
/// Deletes and recreates the test database schema for the given connection string.
/// </summary>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// <summary> | |
| /// Drops and recreates the database schema. Used for test isolation | |
| /// when a test needs a guaranteed clean slate. | |
| /// <summary> | |
| /// Deletes and recreates the test database schema for the given connection string. | |
| /// </summary> | |
| /// <param name="connectionString">The database connection string used to locate the server; if the connection string has no catalog or specifies "master", the configured test database name will be used.</param> | |
| /// <summary> | |
| /// Deletes and recreates the test database schema for the given connection string. | |
| /// </summary> | |
| /// <param name="connectionString">The database connection string used to locate the server; if the connection string has no catalog or specifies "master", the configured test database name will be used.</param> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs`
around lines 67 - 73, The XML doc for the database-reset method contains a
duplicated and unclosed <summary> tag; fix the XML by consolidating the two
summary lines into a single well-formed XML comment (one <summary> ...
</summary>) for the method in DatabaseBootstrap (the method that "Deletes and
recreates the test database schema for the given connection string"), ensuring
the opening <summary> is closed and no duplicate <summary> tag remains so XML
documentation parses correctly.
| /// <summary> | ||
| /// Seeds a complete test scenario with all relationship types represented. | ||
| /// <summary> | ||
| /// Seeds the provided test database with a complete, deterministic integration scenario: three topic tags, two mentors (one with a workspace), a learning catalog containing two courses with policies, tag associations, and a mentor assignment for the first course, then saves the changes to the context. | ||
| /// </summary> | ||
| /// <param name="context">The TestDbContext to which seeded entities are added and persisted.</param> |
There was a problem hiding this comment.
Malformed XML documentation: duplicate <summary> tag.
The XML documentation has two <summary> opening tags without properly closing the first one. This will cause XML doc generation warnings.
📝 Proposed fix
/// <summary>
/// Seeds a complete test scenario with all relationship types represented.
- /// <summary>
- /// Seeds the provided test database with a complete, deterministic integration scenario: three topic tags, two mentors (one with a workspace), a learning catalog containing two courses with policies, tag associations, and a mentor assignment for the first course, then saves the changes to the context.
/// </summary>
+ /// <remarks>
+ /// Seeds the provided test database with a complete, deterministic integration scenario: three topic tags, two mentors (one with a workspace), a learning catalog containing two courses with policies, tag associations, and a mentor assignment for the first course, then saves the changes to the context.
+ /// </remarks>
/// <param name="context">The TestDbContext to which seeded entities are added and persisted.</param>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedData.cs`
around lines 23 - 28, The XML doc for the seeding method in the SeedData class
is malformed due to a duplicate opening <summary> tag; fix it by consolidating
the two <summary> entries into a single well-formed XML summary block (one
opening <summary>, the combined description, then a single closing </summary>),
and ensure the <param name="context"> tag remains outside that summary and
correctly formed; update the comment above the seeding method in SeedData.cs so
XML doc generation warnings are resolved.
| public void UnsupportedNavigationMutatedException_with_empty_strings_stores_them_as_is() | ||
| { | ||
| var ex = new UnsupportedNavigationMutatedException(string.Empty, string.Empty); | ||
|
|
||
| ex.RelationshipPath.Should().Be(string.Empty); | ||
| ex.RelationshipType.Should().Be(string.Empty); | ||
| } |
There was a problem hiding this comment.
Fix failing empty-string test expectation (Line 223).
This test contradicts the current GraphUpdateException validation contract and is already failing CI. It should assert rejection of empty relationshipPath, not successful storage.
Proposed test fix
[Fact]
-public void UnsupportedNavigationMutatedException_with_empty_strings_stores_them_as_is()
+public void UnsupportedNavigationMutatedException_rejects_empty_relationship_path()
{
- var ex = new UnsupportedNavigationMutatedException(string.Empty, string.Empty);
-
- ex.RelationshipPath.Should().Be(string.Empty);
- ex.RelationshipType.Should().Be(string.Empty);
+ var act = () => new UnsupportedNavigationMutatedException(string.Empty, string.Empty);
+
+ act.Should().Throw<ArgumentException>()
+ .Which.ParamName.Should().Be("relationshipPath");
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public void UnsupportedNavigationMutatedException_with_empty_strings_stores_them_as_is() | |
| { | |
| var ex = new UnsupportedNavigationMutatedException(string.Empty, string.Empty); | |
| ex.RelationshipPath.Should().Be(string.Empty); | |
| ex.RelationshipType.Should().Be(string.Empty); | |
| } | |
| [Fact] | |
| public void UnsupportedNavigationMutatedException_rejects_empty_relationship_path() | |
| { | |
| var act = () => new UnsupportedNavigationMutatedException(string.Empty, string.Empty); | |
| act.Should().Throw<ArgumentException>() | |
| .Which.ParamName.Should().Be("relationshipPath"); | |
| } |
🧰 Tools
🪛 GitHub Actions: .NET CI
[error] 225-225: xUnit test failed: Diwink.Extensions.EntityFrameworkCore.Tests.Unit.Exceptions.GraphUpdateExceptionTests.UnsupportedNavigationMutatedException_with_empty_strings_stores_them_as_is. Error: System.ArgumentException: Relationship path cannot be empty or whitespace. (Parameter 'relationshipPath')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore.Tests.Unit/Exceptions/GraphUpdateExceptionTests.cs`
around lines 223 - 229, The test currently asserts that
UnsupportedNavigationMutatedException(string.Empty, string.Empty) stores empty
strings, but per GraphUpdateException validation it should reject an empty
relationshipPath; update the test to assert that constructing
UnsupportedNavigationMutatedException with an empty relationshipPath throws the
appropriate validation exception (e.g., ArgumentException or the specific
validation exception used by GraphUpdateException) instead of expecting
successful storage; reference the UnsupportedNavigationMutatedException
constructor in the assertion.
| public static bool IsRequiredDependent(IForeignKey foreignKey) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(foreignKey); | ||
| return foreignKey.IsRequired; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check if IsRequiredDependent is actually called anywhere in the codebase
rg -n 'IsRequiredDependent' --type=csRepository: WahidBitar/EF-Core-Simple-Graph-Update
Length of output: 245
Remove unused IsRequiredDependent helper method.
The IsRequiredDependent method is not called anywhere in the codebase. Code search confirms only the definition exists with zero call sites, while GraphUpdateOrchestrator.ClassifyNavigation directly accesses foreignKey.IsRequired instead. Remove this dead code unless it's intentionally preserved for public API compatibility.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cs`
around lines 24 - 28, Remove the dead helper method IsRequiredDependent from
OneToOneOwnershipResolver: delete the public static bool
IsRequiredDependent(IForeignKey foreignKey) method (including its
ArgumentNullException.ThrowIfNull and return foreignKey.IsRequired) since
GraphUpdateOrchestrator.ClassifyNavigation already reads foreignKey.IsRequired
and there are no call sites; if intended for public API compatibility, instead
mark it obsolete with an explanatory message and redirect callers to use
foreignKey.IsRequired.
| /// <summary> | ||
| /// Removes the required dependent by marking it for deletion. | ||
| /// EF Core will delete the row on SaveChanges. | ||
| /// <summary> | ||
| /// Marks the dependent entity referenced by <paramref name="existingNavigation"/> for deletion if it is currently tracked. | ||
| /// </summary> | ||
| /// <param name="context">The <see cref="DbContext"/> used to mark the dependent entity for removal.</param> | ||
| /// <param name="existingNavigation">The tracked reference entry whose <c>CurrentValue</c> is the dependent entity to remove; no action is taken if its value is <c>null</c>.</param> |
There was a problem hiding this comment.
Malformed XML documentation: unclosed </summary> and duplicate tags.
Same pattern as other files - Line 16 starts <summary> without closing the previous block at Line 15.
📝 Proposed fix
/// <summary>
/// Removes the required dependent by marking it for deletion.
/// EF Core will delete the row on SaveChanges.
- /// <summary>
- /// Marks the dependent entity referenced by <paramref name="existingNavigation"/> for deletion if it is currently tracked.
/// </summary>
/// <param name="context">The <see cref="DbContext"/> used to mark the dependent entity for removal.</param>
- /// <param name="existingNavigation">The tracked reference entry whose <c>CurrentValue</c> is the dependent entity to remove; no action is taken if its value is <c>null</c>.</param>
+ /// <param name="existingNavigation">The tracked reference entry whose <c>CurrentValue</c> is the dependent entity to remove; no action is taken if <c>CurrentValue</c> is <c>null</c>.</param>
public static void RemoveDependent(DbContext context, ReferenceEntry existingNavigation)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cs`
around lines 13 - 20, Fix the malformed XML doc on RequiredOneToOneStrategy by
closing the first <summary> tag and removing the duplicate summary block so the
method-level documentation is a single well-formed <summary> describing that it
marks the dependent referenced by existingNavigation for deletion; ensure the
param tags remain intact for context and parameters (refer to the
RequiredOneToOneStrategy class and the method that accepts DbContext and
existingNavigation) so the XML comments validate correctly.
| using Diwink.Extensions.EntityFrameworkCore.Exceptions; | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.EntityFrameworkCore.ChangeTracking; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify if System.Linq is already imported elsewhere or if this file compiles
# Check the current imports in the file
cat src/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.cs | head -20
# Check if there's a global using for System.Linq in the project
fd -e cs -p 'GlobalUsings|Usings' --exec cat {} \; 2>/dev/null || echo "No global usings file found"
rg -l 'global using.*System\.Linq' --type csRepository: WahidBitar/EF-Core-Simple-Graph-Update
Length of output: 1056
🏁 Script executed:
# Check line 49 and surrounding context
sed -n '45,55p' src/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.csRepository: WahidBitar/EF-Core-Simple-Graph-Update
Length of output: 656
🏁 Script executed:
# Look for global using statements anywhere in the project
find . -name "*.cs" -type f | xargs grep -l "global using" 2>/dev/null | head -5Repository: WahidBitar/EF-Core-Simple-Graph-Update
Length of output: 64
🏁 Script executed:
# Verify the complete list of usings at the top of the file
head -10 src/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.csRepository: WahidBitar/EF-Core-Simple-Graph-Update
Length of output: 514
Add missing using System.Linq; directive to resolve compilation error.
Line 49 calls .Cast<object>() on collectionEntry.CurrentValue, which is an extension method from System.Linq.Enumerable. This import is required for the code to compile.
Proposed fix
using Diwink.Extensions.EntityFrameworkCore.Exceptions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
+using System.Linq;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| using Diwink.Extensions.EntityFrameworkCore.Exceptions; | |
| using Microsoft.EntityFrameworkCore; | |
| using Microsoft.EntityFrameworkCore.ChangeTracking; | |
| using Diwink.Extensions.EntityFrameworkCore.Exceptions; | |
| using Microsoft.EntityFrameworkCore; | |
| using Microsoft.EntityFrameworkCore.ChangeTracking; | |
| using System.Linq; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.cs`
around lines 1 - 3, The file NavigationLoadGuard.cs is missing the System.Linq
namespace causing the call to collectionEntry.CurrentValue.Cast<object>() to not
compile; add the using System.Linq; directive at the top of the file (alongside
existing usings) so the Enumerable.Cast<T>() extension is available for code in
the NavigationLoadGuard class and any methods referencing
collectionEntry.CurrentValue.Cast<object>().
| /// <summary> | ||
| /// Ensures all navigations in the existing entity entry that the caller expects | ||
| /// to participate in graph mutation are explicitly loaded. | ||
| /// <summary> | ||
| /// Validates loaded navigations on the tracked entity and its reachable object graph, ensuring required navigations are present before mutation. | ||
| /// </summary> | ||
| /// <param name="existingEntry">The tracked entity entry whose navigations and reachable entities will be validated.</param> | ||
| /// <exception cref="UnloadedNavigationMutationException">Thrown when a required navigation encountered during validation is not loaded.</exception> |
There was a problem hiding this comment.
XML documentation has unclosed <summary> tags.
Line 17 and line 66 both have <summary> where </summary> is expected. This creates malformed XML documentation.
📝 Proposed fix for line 17
/// <summary>
/// Ensures all navigations in the existing entity entry that the caller expects
/// to participate in graph mutation are explicitly loaded.
- /// <summary>
+ /// </summary>
/// <summary>
/// Validates loaded navigations on the tracked entity and its reachable object graph, ensuring required navigations are present before mutation.
/// </summary>Note: There appear to be two summary blocks here. Consider consolidating into a single <summary> block.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.cs`
around lines 14 - 21, The XML doc for NavigationLoadGuard has malformed/unclosed
<summary> tags (duplicate opening tags at the top comment) — consolidate the two
<summary> blocks into a single well-formed <summary> and ensure each opening
<summary> has a matching </summary>, then update the surrounding XML (the <param
name="existingEntry"> and <exception cref="UnloadedNavigationMutationException">
lines) so they remain inside the single summary/remarks block; locate the
comment above the NavigationLoadGuard declaration and fix the tags accordingly.
| /// <summary> | ||
| /// Validates that a specific navigation is loaded before attempting mutation on it. | ||
| /// Throws <see cref="UnloadedNavigationMutationException"/> if not. | ||
| /// <summary> | ||
| /// Ensures the specified navigation is loaded before performing a mutation. | ||
| /// </summary> | ||
| /// <param name="navigation">The navigation entry to validate is loaded.</param> | ||
| /// <param name="entityPath">A slash-delimited path that identifies the entity location used in the exception message.</param> | ||
| /// <exception cref="UnloadedNavigationMutationException">Thrown when <paramref name="navigation"/> is not loaded.</exception> |
There was a problem hiding this comment.
Same XML documentation typo at line 66.
📝 Proposed fix
/// <summary>
/// Validates that a specific navigation is loaded before attempting mutation on it.
/// Throws <see cref="UnloadedNavigationMutationException"/> if not.
- /// <summary>
+ /// </summary>
+ /// <remarks>
/// Ensures the specified navigation is loaded before performing a mutation.
- /// </summary>
+ /// </remarks>
/// <param name="navigation">The navigation entry to validate is loaded.</param>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.cs`
around lines 63 - 71, The XML documentation for the method in
NavigationLoadGuard (ValidateNavigationLoaded) contains a typo: an opening
<summary> tag is used twice instead of closing the first; replace the second
"<summary>" with a closing "</summary>" (or remove the duplicate) so the XML doc
is well-formed and the summary reads: "Validates that a specific navigation is
loaded before attempting mutation on it. Throws <see
cref=\"UnloadedNavigationMutationException\"/> if not." Ensure the parameter and
exception XML tags remain unchanged.
…cs v2 rebuild: EF Core 10 targeting with modular refactoring @coderabbitai (#16) * refactored everything O_o * huge renaming * build(ci): update github actions and fix yaml formatting Update actions/checkout from v2 to v4, actions/setup-dotnet from v1 to v4, and change dotnet-version to "10.0.x". Also fix indentation and remove extra spaces in branch names. * ci: enhance workflow with publishing and version bump Add a new publish job to the CI pipeline that builds, packs, and publishes the NuGet package on master pushes, with conditional checks for API key availability. Update action versions (checkout@v6, setup-dotnet@v5), adjust commands to target the solution file, and refine job names and permissions. BREAKING CHANGE: package version bumped from 2.0.0 to 10.0.0 to align with EF Core 10+ targeting, which may introduce incompatibilities. * Rebuild graph update system with contract-driven relationship semantics; targeting EF Core 10 * 📝 Add docstrings to `001-rebuild-graph-update` Docstrings generation was requested by @WahidBitar. * #13 (comment) The following files were modified: * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CourseConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CourseMentorAssignmentConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/CoursePolicyConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/LearningCatalogConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/MentorWorkspaceConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/Configurations/TopicTagConfiguration.cs` * `src/Diwink.Extensions.EntityFrameworkCore.TestModel/TestDbContext.cs` * `src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/DatabaseBootstrap.cs` * `src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/IntegrationTestBase.cs` * `src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SeedData.cs` * `src/Diwink.Extensions.EntityFrameworkCore.Tests.Integration/Infrastructure/SqlServerContainerFixture.cs` * `src/Diwink.Extensions.EntityFrameworkCore/DbContextExtensions.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/AmbiguousOwnershipSemanticsException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/GraphUpdateException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/PartialMutationNotAllowedException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnloadedNavigationMutationException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedNavigationMutatedException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Exceptions/UnsupportedRelationshipPatternException.cs` * `src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/EntityKeyHelper.cs` * `src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/GraphUpdateOrchestrator.cs` * `src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/OperationGuard.cs` * `src/Diwink.Extensions.EntityFrameworkCore/GraphUpdate/RelatedEntityMutationService.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OneToOneOwnershipResolver.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/OptionalOneToOneStrategy.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PayloadManyToManyStrategy.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/PureManyToManyStrategy.cs` * `src/Diwink.Extensions.EntityFrameworkCore/RelationshipStrategies/RequiredOneToOneStrategy.cs` * `src/Diwink.Extensions.EntityFrameworkCore/Traversal/NavigationLoadGuard.cs` * CodeRabbit Generated Unit Tests: Add unit tests for PR changes * Harden graph update helpers and test infrastructure * Preserve modified tracked many-to-many entities * Rename graph update API and remove mutation service --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Summary by CodeRabbit
New Features
Tests
Documentation
Chores